package bonsai
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
A library for building dynamic webapps, using Js_of_ocaml
Install
dune-project
Dependency
Authors
Maintainers
Sources
v0.17.0.tar.gz
sha256=c78c4476ee6b856846e2d0941e5965009d5e1b853e564b2b1bee61202f0b1ebb
doc/src/bonsai.web/forward_performance_entries.ml.html
Source file forward_performance_entries.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 220open Js_of_ocaml open Async_kernel open! Core open Bonsai.Private open Bonsai_protocol type 'result t = { instrumented_computation : 'result Bonsai.Private.Computation.t ; shutdown : unit -> unit } module Worker : sig (** Represents a web worker that you can send messages to. This type handles annoying details such as making sure that the web worker is ready to start receiving messages, serializing the messages, and batching several of the messages together. *) type t (** Loads a web worker from the specified URL. [on_message] is called every time the web worker sends a message to the main thread. *) val create : url:string -> on_message:(t -> string -> unit) -> t (** Queues a message to be sent at the next call to [flush]. *) val send_message : t -> Worker_message.t -> unit (** Sends all the queued messages to the worker as a single message *) val flush : t -> unit val set_error_handler : t -> f:(Worker.errorEvent Js.t -> unit) -> unit val shutdown : t -> unit end = struct (* The [acknowledged] field keeps track of whether the worker has sent back a message, which means that it is ready to receive messages. *) type t = { mutable acknowledged : bool ; mutable buffer : Worker_message.t Reversed_list.t ; worker : (Js.js_string Js.t, Js.js_string Js.t) Worker.worker Js.t } let create ~url ~on_message = (* We use a [blob] to circumvent the same-origin policy for web workers. Note that we aren't able to break through the browser's defenses totally, since the server must still configure its CSP to allow web workers from blobs. *) let worker = let blob = File.blob_from_string ~contentType:"application/javascript" [%string "importScripts('%{url}')"] in let blob_url = Dom_html.window##._URL##createObjectURL blob in Worker.create (Js.to_string blob_url) in let result = { worker; acknowledged = false; buffer = [] } in worker##.onmessage := Dom.handler (fun (message : Js.js_string Js.t Worker.messageEvent Js.t) -> result.acknowledged <- true; on_message result (Js.to_string message##.data); Js._false); result ;; let set_error_handler t ~f = t.worker##.onerror := Dom.handler (fun error_message -> f error_message; Js._false) ;; let send_message t message = t.buffer <- message :: t.buffer let flush t = if t.acknowledged then ( let message = Versioned_message.V4 (Reversed_list.rev t.buffer) in let js_string = Js.bytestring (Bin_prot.Writer.to_string Versioned_message.bin_writer_t message) in t.worker##postMessage js_string; t.buffer <- []) else () ;; let shutdown t = t.buffer <- []; t.worker##terminate ;; end let iter_entries performance_observer_entry_list ~f = performance_observer_entry_list##getEntries |> Js.to_array |> Array.iter ~f:(fun entry -> let label = let label = entry##.name |> Js.to_string in match Instrumentation.extract_node_path_from_entry_label label with | None -> `Other label | Some node_id -> `Bonsai node_id in let entry_type = entry##.entryType |> Js.to_bytestring in let start_time = entry##.startTime in let duration = entry##.duration in f { Entry.label; entry_type; start_time; duration }) ;; let uuid_to_url ~host ~port uuid = [%string "https://%{host}:%{port#Int}/%{uuid#Uuid}"] let generate_uuid () = let random_state = Random.State.default in Uuid.create_random random_state ;; let instrument ~host ~port ~worker_name component = let uuid, reused_uuid = let key = Js.string "bonsai-bug-session-uuid" in match Js.Optdef.to_option Dom_html.window##.sessionStorage with | None -> print_endline "No session storage; generating new session uuid"; generate_uuid (), false | Some storage -> (match Js.Opt.to_option (storage##getItem key) with | None -> print_endline "No prior session uuid found; generating a new one."; let uuid = generate_uuid () in storage##setItem key (Js.string (Uuid.to_string uuid)); uuid, false | Some uuid_string -> (match Option.try_with (fun () -> Uuid.of_string (Js.to_string uuid_string)) with | None -> print_endline "Found existing session uuid, but could not parse it; generating a new one."; let uuid = generate_uuid () in storage##setItem key (Js.string (Uuid.to_string uuid)); uuid, false | Some uuid -> print_endline "Re-using existing session uuid. If you no longer have the debugger window \ open, you can use the following link:"; print_endline (uuid_to_url ~host ~port uuid); uuid, true)) in if not reused_uuid then ( let url = uuid_to_url ~host ~port uuid in Dom_html.window##open_ (Js.string url) (Js.string "bonsai-bug") (Js.Opt.return (Js.string "noopener")) |> (ignore : Dom_html.window Js.t Js.opt -> unit)); let graph_info_dirty = ref false in let graph_info = ref Graph_info.empty in let stop_ivar = Ivar.create () in let on_first_message worker = Worker.send_message worker (Uuid uuid); graph_info_dirty := true; let stop = Ivar.read stop_ivar in Async_kernel.every ~stop (Time_ns.Span.of_sec 0.2) (fun () -> if !graph_info_dirty then ( graph_info_dirty := false; Worker.send_message worker (Message (Graph_info !graph_info))); Worker.flush worker; Javascript_profiling.clear_marks (); Javascript_profiling.clear_measures ()); let performance_observer = let f new_entries observer = observer##takeRecords |> (ignore : PerformanceObserver.performanceEntry Js.t Js.js_array Js.t -> unit); iter_entries new_entries ~f:(fun entry -> Worker.send_message worker (Message (Performance_measure entry))) in PerformanceObserver.observe ~entry_types:[ "measure" ] ~f in Deferred.upon stop (fun () -> performance_observer##disconnect; Javascript_profiling.clear_marks (); Javascript_profiling.clear_measures (); Worker.shutdown worker) in let worker = (* We have no way of knowing when the web worker has set up its [onmessage] callback and is ready to receive messages. Thus, before sending any messages to it, we first wait until it is sends an acknowledgement message. *) let got_first_message = ref false in Worker.create ~url:[%string "https://%{host}:%{port#Int}/%{worker_name}"] ~on_message:(fun worker _ -> if not !got_first_message then got_first_message := true; on_first_message worker) in let component = Bonsai.Private.Graph_info.iter_graph_updates component ~on_update:(fun gi -> (* Instead of sending a message every time the graph changes, we maintain the current graph_info and mark it as dirty, so that the [every] loop send a single message per flush. *) graph_info := gi; graph_info_dirty := true) in let instrumented_computation = Instrumentation.instrument_computation component ~start_timer:(fun s -> Javascript_profiling.Manual.mark (s ^ "before")) ~stop_timer:(fun s -> let before = s ^ "before" in let after = s ^ "after" in Javascript_profiling.Manual.mark after; Javascript_profiling.Manual.measure ~name:s ~start:before ~end_:after) in let shutdown () = Ivar.fill_if_empty stop_ivar () in let shutdown () = match Or_error.try_with shutdown with | Ok () -> () | Error e -> eprint_s [%sexp (e : Error.t)] in Worker.set_error_handler worker ~f:(fun message -> Firebug.console##warn message; shutdown ()); { instrumented_computation; shutdown } ;;
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>