package memtrace_viewer
Interactive memory profiler based on Memtrace
Install
Dune Dependency
Authors
Maintainers
Sources
memtrace_viewer-v0.16.0.tar.gz
sha256=bb50fc48fef748dffe7ff1e151021b1361500c432a8c2991065fd31fd474f817
doc/src/memtrace_viewer.native/location_trie.ml.html
Source file location_trie.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
open! Core open Memtrace_viewer_common module Loc_hitters = Substring_heavy_hitters.Make (Location) let find_heavy_hitters ~trace ~tolerance ~significance_frequency : Loc_hitters.t * Filtered_trace.Call_sites.t = let shh = Loc_hitters.create ~tolerance in let first = ref true in let call_sites = Filtered_trace.iter_and_gather_call_sites ~mode:Preserve_backtraces trace (fun _time ev -> match ev with | Alloc { obj_id = _ ; nsamples ; source = _ ; single_allocation_size = _ ; size = _ ; backtrace_buffer ; backtrace_length ; common_prefix } -> (* Important: Memtrace order of stack frames is *toplevel first*, i.e. the opposite order to how stack traces are usually displayed. Reversing this here is not feasible as SHH's performance relies heavily on passing [common_prefix] through. XXX For now, I'm deciding to keep a consistent order for all in-memory representations of backtraces and flip it around only on display to the user. Another defensible choice would be to have the server side flip it around before sending to the client side (say, by having [Loc_hitters_as_suffix_trie] perform the reversal somehow), possibly relying on a wrapper type like [Data.Backtrace.Reversed.t] to keep them straight. (Could even have [Data.Backtrace.Toplevel_first.t] and [Data.Backtrace.Alloc_first.t].) Make enough space for the toplevel location, the backtrace minus the common prefix, and the allocator location. *) let backtrace_length_after_common_prefix = backtrace_length - common_prefix in let space_for_added_toplevel = (* Make room for the [Location.toplevel] in the very first backtrace. Subsequent backtraces will include it in the common prefix. *) if !first then 1 else 0 in let space_for_added_allocator = (* Always there at the end *) 1 in let word_len = space_for_added_toplevel + backtrace_length_after_common_prefix + space_for_added_allocator in let word = Array.create ~len:word_len Location.dummy in if !first then word.(0) <- Location.toplevel; word.(word_len - 1) <- Location.allocator; Array.blit ~src:backtrace_buffer ~src_pos:common_prefix ~dst:word ~dst_pos:space_for_added_toplevel ~len:backtrace_length_after_common_prefix; let common_prefix = if !first then 0 else (* Account for the [Location.toplevel] we added the very first time *) common_prefix + 1 in first := false; Loc_hitters.insert shh word ~count:nsamples ~common_prefix | Promote _ | Collect _ | End -> ()) in Loc_hitters.calculate_totals shh ~heaviness_frequency:significance_frequency; shh, call_sites ;; let bytes_of_samples ~rate ~word_size samples = let words = Float.of_int samples /. rate in Byte_units.scale word_size words ;; (* Make sure we're using the same criteria when pruning the trie or dropping call sites *) let is_significant ~shh node = Loc_hitters.contains_heavy shh node module Loc_hitters_as_suffix_tree : sig include Data.Suffix_tree val of_loc_hitters : loc_cache:Location.Cache.t -> sample_rate:float -> word_size:Byte_units.t -> Loc_hitters.t -> t val total_allocations : t -> Byte_units.t end = struct module Hitter_subnode_id : sig type t include Hashable.S with type t := t include Sexpable.S with type t := t val of_ : node:Loc_hitters.Node.t -> edge_index:int -> t end = struct module T = struct type t = { node_id : int ; edge_index : int } [@@deriving hash, compare, sexp] end include T include Hashable.Make (T) let of_ ~node ~edge_index = { node_id = (Loc_hitters.Node.id node :> int); edge_index } ;; end module Trie = struct type t = { shh : Loc_hitters.t ; sample_rate : float ; word_size : Byte_units.t ; suffix_cache : node Hitter_subnode_id.Table.t ; loc_cache : Location.Cache.t } and node = { trie : t ; node : Loc_hitters.Node.t ; edge_index : int } let of_loc_hitters ~loc_cache ~sample_rate ~word_size shh = let suffix_cache = Hitter_subnode_id.Table.create () in { shh; sample_rate; word_size; suffix_cache; loc_cache } ;; let real_root t = Loc_hitters.root t.shh let root t = let node = real_root t |> Loc_hitters.Node.Root.node in { trie = t; node; edge_index = -1 } ;; let bytes_of_samples t samples = bytes_of_samples samples ~rate:t.sample_rate ~word_size:t.word_size ;; let total_allocations t = Loc_hitters.total_count t.shh |> bytes_of_samples t let loc_data t loc = Location.Cache.get_loc_data t.loc_cache loc end module Node = struct module Id = Hitter_subnode_id module T : sig type t = Trie.node = private { trie : Trie.t ; node : Loc_hitters.Node.t ; edge_index : int } val mk : trie:Trie.t -> node:Loc_hitters.Node.t -> edge_index:int -> t end = struct type t = Trie.node = { trie : Trie.t ; node : Loc_hitters.Node.t ; edge_index : int } let mk ~trie ~node ~edge_index = let () = assert (edge_index < Loc_hitters.Node.edge_length node) in { trie; node; edge_index } ;; end include T let incoming_edge { trie; node; edge_index } = if edge_index < 0 then (* Root node *) Data.Location.dummy else Loc_hitters.Node.edge_char node edge_index |> Trie.loc_data trie ;; let next_pos_along_edge node edge_index = let next_edge_index = edge_index + 1 in if next_edge_index < Loc_hitters.Node.edge_length node then Some next_edge_index else None ;; let children { trie; node; edge_index } = match next_pos_along_edge node edge_index with | Some next_edge_index -> let key = Loc_hitters.Node.edge_char node next_edge_index in let key_data = Trie.loc_data trie key in [ key_data, mk ~trie ~node ~edge_index:next_edge_index ] | None -> let root = Loc_hitters.root trie.shh in Loc_hitters.Node.fold_children node ~root ~init:[] ~f:(fun child children -> if is_significant ~shh:trie.shh child then ( let key = Loc_hitters.Node.edge_char child 0 in let key_data = Trie.loc_data trie key in (key_data, mk ~trie ~node:child ~edge_index:0) :: children) else children) ;; module Debug = struct type nonrec t = t let sexp_of_t { trie = _; node; edge_index } = [%message (edge_index : int) (node : Loc_hitters.Node.Debug_full.t)] ;; end let find_suffix ~trie node edge_index = let is_root = phys_equal (Loc_hitters.Node.parent node) node in if is_root then None else ( (* The semantics of the suffix link is defined according to the entire edge. In other words, [suffix] with its *entire* edge represents the suffix of [node] with its *entire* edge. In general, we start somewhere up [node]'s edge, so we begin by overapproximating: go to the end of [suffix] and then move up to compensate. *) assert (Loc_hitters.Node.has_suffix node); let suffix = Loc_hitters.Node.suffix node in let distance_to_move_up = Loc_hitters.Node.edge_length node - 1 - edge_index in let rec loop ~suffix ~distance_to_move_up = let is_root = phys_equal suffix (Loc_hitters.Node.parent suffix) in let edge_length = Loc_hitters.Node.edge_length suffix in let bottom_edge_index = edge_length - 1 in if is_root then ( assert (distance_to_move_up = 0); Some (Trie.root trie)) else if distance_to_move_up <= bottom_edge_index then ( let edge_index = bottom_edge_index - distance_to_move_up in Some (mk ~trie ~node:suffix ~edge_index)) else ( let suffix = Loc_hitters.Node.parent suffix in let distance_to_move_up = distance_to_move_up - edge_length in loop ~suffix ~distance_to_move_up) in loop ~suffix ~distance_to_move_up) ;; let suffix { trie; node; edge_index } = find_suffix ~trie node edge_index let entry { trie; node; edge_index = _ } = let total_allocations_in_trie = Trie.total_allocations trie in let allocations = Loc_hitters.Node.total_count node |> Trie.bytes_of_samples trie in let direct_allocations = Loc_hitters.Node.light_count node |> Trie.bytes_of_samples trie in let is_heavy = Loc_hitters.is_heavy trie.shh node in Data.Entry.create ~total_allocations_in_trie ~allocations ~direct_allocations ~is_heavy ;; let id { trie = _; node; edge_index } = Hitter_subnode_id.of_ ~node ~edge_index let representative { trie; node; edge_index = _ } = let repr = Loc_hitters.Node.representative node in (* The representative is always the longest fragment in its class, so pick the bottom subnode *) let edge_index = Loc_hitters.Node.edge_length repr - 1 in mk ~trie ~node:repr ~edge_index ;; end include Trie end let find_call_node ~caller ~callee ~shh = let root = Loc_hitters.root shh in let%bind.Option caller_node = Loc_hitters.Node.get_child_opt ~root (Loc_hitters.Node.Root.node root) caller in if Loc_hitters.Node.edge_length caller_node > 1 then if Location.equal callee (Loc_hitters.Node.edge_char caller_node 1) then Some caller_node else None else Loc_hitters.Node.get_child_opt ~root caller_node callee ;; let keep_call_site ~caller ~callee ~shh ~loc_cache = let root = Loc_hitters.root shh in (* This heuristic tends to drop call sites that only occur at recursion depth >1, since the true caller will have been filtered out of the backtrace. In the case of the actual allocation, though, we can compensate simply by taking the total over all callers, since there can only have been the one true caller. *) let call_node = match Location.Cache.get_loc_data loc_cache callee with | Allocation_site _ -> Loc_hitters.Node.get_child_opt ~root (Loc_hitters.Node.Root.node root) callee | Function _ | Allocator | Toplevel | Dummy -> find_call_node ~caller ~callee ~shh in match call_node with | None -> false | Some node -> is_significant ~shh node ;; let trie_of_shh ~loc_cache ~rate ~word_size ~all_call_sites shh = let suffix_tree = Loc_hitters_as_suffix_tree.of_loc_hitters ~loc_cache ~sample_rate:rate ~word_size shh in let total_allocations = Loc_hitters_as_suffix_tree.total_allocations suffix_tree in let trie = Data.Fragment_trie.of_suffix_tree (module Loc_hitters_as_suffix_tree) suffix_tree ~total_allocations in let call_sites = all_call_sites |> Hashtbl.to_alist |> List.map ~f:(fun (caller, all_call_sites_in_caller) -> let call_sites_and_callees = Hashtbl.to_alist all_call_sites_in_caller in let call_sites = List.filter_map call_sites_and_callees ~f:(fun (call_site, callees) -> if Hash_set.exists callees ~f:(fun callee -> keep_call_site ~caller ~callee ~shh ~loc_cache) then Some call_site else None) in let call_sites = match call_sites with | _ :: _ -> call_sites | [] -> (* Everything was pruned, but we'd like to list at least _one_ call site, so pick the first one *) let compare cs1 cs2 = let get_data = Location.Cache.get_call_site_data loc_cache in Data.Call_site.compare (get_data cs1) (get_data cs2) in (match List.sort call_sites ~compare with | call_site :: _ -> [ call_site ] | [] -> (* ??? *) []) in let call_sites = List.map ~f:(Location.Cache.get_call_site_data loc_cache) call_sites in let caller = match Location.Cache.get_loc_data loc_cache caller with | Function data -> data | (Allocation_site _ | Toplevel | Allocator | Dummy) as data -> raise_s [%message "Unexpected location data for caller" (data : Data.Location.t)] in caller, call_sites) |> Data.Call_sites.create in trie, call_sites ;; let build ~trace ~loc_cache ~tolerance ~significance_frequency = let rate = Filtered_trace.sample_rate trace in let word_size = Filtered_trace.word_size trace in let shh, all_call_sites = find_heavy_hitters ~trace ~tolerance ~significance_frequency in trie_of_shh shh ~loc_cache ~rate ~word_size ~all_call_sites ;;
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>