package bonsai
A library for building dynamic webapps, using Js_of_ocaml
Install
Dune Dependency
Authors
Maintainers
Sources
v0.15.1.tar.gz
sha256=0c4a714146073f421f1a6179561f836b45d8dc012c743207d3481ea63bef74bf
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 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
open Js_of_ocaml (* This module has been upstreamed into js_of_ocaml, so we should remove it once the new compiler gets released to opam. *) module PerformanceObserver : sig (* Js_of_ocaml library * http://www.ocsigen.org/js_of_ocaml/ * Copyright (C) 2021 Philip White * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** PerformanceObserver API A code example: {[ if (PerformanceObserver.is_supported()) then let entry_types = [ "measure" ] in let f entries observer = let entries = entries##getEntries in Firebug.console##debug entries ; Firebug.console##debug observer in PerformanceObserver.observe ~entry_types ~f () ]} @see <https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver> for API documentation. *) class type performanceObserverInit = object method entryTypes : Js.js_string Js.t Js.js_array Js.t Js.writeonly_prop end class type performanceEntry = object method name : Js.js_string Js.t Js.readonly_prop method entryType : Js.js_string Js.t Js.readonly_prop method startTime : float Js.readonly_prop method duration : float Js.readonly_prop end class type performanceObserverEntryList = object method getEntries : performanceEntry Js.t Js.js_array Js.t Js.meth end class type performanceObserver = object method observe : performanceObserverInit Js.t -> unit Js.meth method disconnect : unit Js.meth method takeRecords : performanceEntry Js.t Js.js_array Js.t Js.meth end val observe : entry_types:string list -> f:(performanceObserverEntryList Js.t -> performanceObserver Js.t -> unit) -> performanceObserver Js.t end = struct open Js_of_ocaml class type performanceObserverInit = object method entryTypes : Js.js_string Js.t Js.js_array Js.t Js.writeonly_prop end class type performanceEntry = object method name : Js.js_string Js.t Js.readonly_prop method entryType : Js.js_string Js.t Js.readonly_prop method startTime : float Js.readonly_prop method duration : float Js.readonly_prop end class type performanceObserverEntryList = object method getEntries : performanceEntry Js.t Js.js_array Js.t Js.meth end class type performanceObserver = object method observe : performanceObserverInit Js.t -> unit Js.meth method disconnect : unit Js.meth method takeRecords : performanceEntry Js.t Js.js_array Js.t Js.meth end let performanceObserver = Js.Unsafe.global##._PerformanceObserver let performanceObserver : ((performanceObserverEntryList Js.t -> performanceObserver Js.t -> unit) Js.callback -> performanceObserver Js.t) Js.constr = performanceObserver ;; let observe ~entry_types ~f = let entry_types = entry_types |> List.map Js.string |> Array.of_list |> Js.array in let performance_observer_init : performanceObserverInit Js.t = Js.Unsafe.obj [||] in let () = performance_observer_init##.entryTypes := entry_types in let obs = new%js performanceObserver (Js.wrap_callback f) in let () = obs##observe performance_observer_init in obs ;; end open! Core open Bonsai.Private open Bonsai_protocol type ('model, 'static_action, 'dynamic_action, 'result) t = { instrumented_computation : ('model, 'static_action, 'dynamic_action, '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 'a 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:(string -> unit) -> bin_writer_t:'a Bin_prot.Type_class.writer -> 'a t (** Queues a message to be sent at the next call to [flush]. *) val send_message : 'a t -> 'a -> 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 'a t = { mutable acknowledged : bool ; mutable buffer : 'a list ; worker : (Js.js_string Js.t, Js.js_string Js.t) Worker.worker Js.t ; bin_writer_t : 'a Bin_prot.Type_class.writer } let create ~url ~on_message ~bin_writer_t = (* 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 = []; bin_writer_t } in worker##.onmessage := Dom.handler (fun (message : Js.js_string Js.t Worker.messageEvent Js.t) -> result.acknowledged <- true; on_message (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 js_string = Js.bytestring (Bin_prot.Writer.to_string (List.bin_writer_t t.bin_writer_t) t.buffer) 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 |> Js.to_float in let duration = entry##.duration |> Js.to_float in f { Entry.label; entry_type; start_time; duration }) ;; let instrument ~host ~port ~worker_name component = let worker = (* Once the worker sends an acknowledgement message, we can send the graph info. It's possible that the worker has already received a info message, but we're sending one now, just to be sure. The reason we need to do it this way is that we have no way of knowing when the web worker has set up its [onmessage] callback and is ready to receive message. Thus, we wait until the worker notifies us explicitly that it is ready to receive messages. This onmessage callback is also a convenient place to receive the UUID that identifies this profiling session. Since web workers cannot open new windows, we must open the window from the main page. To keep the URL of the server decoupled from this logic, we just receive the URL from the worker. *) Worker.create ~url:[%string "https://%{host}:%{port#Int}/%{worker_name}"] ~on_message:(fun url -> 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)) ~bin_writer_t:Message.bin_writer_t in 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 (Performance_measure entry)) in PerformanceObserver.observe ~entry_types:[ "measure" ] ~f in let graph_info_dirty = ref false in let graph_info = ref Graph_info.empty 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 loop at the bottom of this function can send only one 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 stop_ivar = Async_kernel.Ivar.create () in let stop = Async_kernel.Ivar.read stop_ivar in Async_kernel.every ~stop (Time_ns.Span.of_sec 0.5) (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 shutdown () = Async_kernel.Ivar.fill_if_empty stop_ivar (); performance_observer##disconnect; Javascript_profiling.clear_marks (); Javascript_profiling.clear_measures (); Worker.shutdown worker 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)"
>