123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311openJs_of_ocaml(* This module has been upstreamed into js_of_ocaml, so we should remove it
once the new compiler gets released to opam. *)modulePerformanceObserver: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.
*)classtypeperformanceObserverInit=objectmethodentryTypes:Js.js_stringJs.tJs.js_arrayJs.tJs.writeonly_propendclasstypeperformanceEntry=objectmethodname:Js.js_stringJs.tJs.readonly_propmethodentryType:Js.js_stringJs.tJs.readonly_propmethodstartTime:floatJs.readonly_propmethodduration:floatJs.readonly_propendclasstypeperformanceObserverEntryList=objectmethodgetEntries:performanceEntryJs.tJs.js_arrayJs.tJs.methendclasstypeperformanceObserver=objectmethodobserve:performanceObserverInitJs.t->unitJs.methmethoddisconnect:unitJs.methmethodtakeRecords:performanceEntryJs.tJs.js_arrayJs.tJs.methendvalobserve:entry_types:stringlist->f:(performanceObserverEntryListJs.t->performanceObserverJs.t->unit)->performanceObserverJs.tend=structopenJs_of_ocamlclasstypeperformanceObserverInit=objectmethodentryTypes:Js.js_stringJs.tJs.js_arrayJs.tJs.writeonly_propendclasstypeperformanceEntry=objectmethodname:Js.js_stringJs.tJs.readonly_propmethodentryType:Js.js_stringJs.tJs.readonly_propmethodstartTime:floatJs.readonly_propmethodduration:floatJs.readonly_propendclasstypeperformanceObserverEntryList=objectmethodgetEntries:performanceEntryJs.tJs.js_arrayJs.tJs.methendclasstypeperformanceObserver=objectmethodobserve:performanceObserverInitJs.t->unitJs.methmethoddisconnect:unitJs.methmethodtakeRecords:performanceEntryJs.tJs.js_arrayJs.tJs.methendletperformanceObserver=Js.Unsafe.global##._PerformanceObserverletperformanceObserver:((performanceObserverEntryListJs.t->performanceObserverJs.t->unit)Js.callback->performanceObserverJs.t)Js.constr=performanceObserver;;letobserve~entry_types~f=letentry_types=entry_types|>List.mapJs.string|>Array.of_list|>Js.arrayinletperformance_observer_init:performanceObserverInitJs.t=Js.Unsafe.obj[||]inlet()=performance_observer_init##.entryTypes:=entry_typesinletobs=new%jsperformanceObserver(Js.wrap_callbackf)inlet()=obs##observeperformance_observer_initinobs;;endopen!CoreopenBonsai.PrivateopenBonsai_protocoltype('model,'static_action,'dynamic_action,'result)t={instrumented_computation:('model,'static_action,'dynamic_action,'result)Bonsai.Private.Computation.t;shutdown:unit->unit}moduleWorker: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'at(** Loads a web worker from the specified URL. [on_message] is called every
time the web worker sends a message to the main thread. *)valcreate:url:string->on_message:(string->unit)->bin_writer_t:'aBin_prot.Type_class.writer->'at(** Queues a message to be sent at the next call to [flush]. *)valsend_message:'at->'a->unit(** Sends all the queued messages to the worker as a single message *)valflush:_t->unitvalset_error_handler:_t->f:(Worker.errorEventJs.t->unit)->unitvalshutdown:_t->unitend=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'at={mutableacknowledged:bool;mutablebuffer:'alist;worker:(Js.js_stringJs.t,Js.js_stringJs.t)Worker.workerJs.t;bin_writer_t:'aBin_prot.Type_class.writer}letcreate~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. *)letworker=letblob=File.blob_from_string~contentType:"application/javascript"[%string"importScripts('%{url}')"]inletblob_url=Dom_html.window##._URL##createObjectURLblobinWorker.create(Js.to_stringblob_url)inletresult={worker;acknowledged=false;buffer=[];bin_writer_t}inworker##.onmessage:=Dom.handler(fun(message:Js.js_stringJs.tWorker.messageEventJs.t)->result.acknowledged<-true;on_message(Js.to_stringmessage##.data);Js._false);result;;letset_error_handlert~f=t.worker##.onerror:=Dom.handler(funerror_message->ferror_message;Js._false);;letsend_messagetmessage=t.buffer<-message::t.bufferletflusht=ift.acknowledgedthen(letjs_string=Js.bytestring(Bin_prot.Writer.to_string(List.bin_writer_tt.bin_writer_t)t.buffer)int.worker##postMessagejs_string;t.buffer<-[])else();;letshutdownt=t.buffer<-[];t.worker##terminate;;endletiter_entriesperformance_observer_entry_list~f=performance_observer_entry_list##getEntries|>Js.to_array|>Array.iter~f:(funentry->letlabel=letlabel=entry##.name|>Js.to_stringinmatchInstrumentation.extract_node_path_from_entry_labellabelwith|None->`Otherlabel|Somenode_id->`Bonsainode_idinletentry_type=entry##.entryType|>Js.to_bytestringinletstart_time=entry##.startTime|>Js.to_floatinletduration=entry##.duration|>Js.to_floatinf{Entry.label;entry_type;start_time;duration});;letinstrument~host~port~worker_namecomponent=letworker=(* 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:(funurl->Dom_html.window##open_(Js.stringurl)(Js.string"bonsai-bug")(Js.Opt.return(Js.string"noopener"))|>(ignore:Dom_html.windowJs.tJs.opt->unit))~bin_writer_t:Message.bin_writer_tinletperformance_observer=letfnew_entriesobserver=observer##takeRecords|>(ignore:PerformanceObserver.performanceEntryJs.tJs.js_arrayJs.t->unit);iter_entriesnew_entries~f:(funentry->Worker.send_messageworker(Performance_measureentry))inPerformanceObserver.observe~entry_types:["measure"]~finletgraph_info_dirty=reffalseinletgraph_info=refGraph_info.emptyinletcomponent=Bonsai.Private.Graph_info.iter_graph_updatescomponent~on_update:(fungi->(* 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)inletinstrumented_computation=Instrumentation.instrument_computationcomponent~start_timer:(funs->Javascript_profiling.Manual.mark(s^"before"))~stop_timer:(funs->letbefore=s^"before"inletafter=s^"after"inJavascript_profiling.Manual.markafter;Javascript_profiling.Manual.measure~name:s~start:before~end_:after)inletstop_ivar=Async_kernel.Ivar.create()inletstop=Async_kernel.Ivar.readstop_ivarinAsync_kernel.every~stop(Time_ns.Span.of_sec0.5)(fun()->if!graph_info_dirtythen(graph_info_dirty:=false;Worker.send_messageworker(Message.Graph_info!graph_info));Worker.flushworker;Javascript_profiling.clear_marks();Javascript_profiling.clear_measures());letshutdown()=Async_kernel.Ivar.fill_if_emptystop_ivar();performance_observer##disconnect;Javascript_profiling.clear_marks();Javascript_profiling.clear_measures();Worker.shutdownworkerinletshutdown()=matchOr_error.try_withshutdownwith|Ok()->()|Errore->eprint_s[%sexp(e:Error.t)]inWorker.set_error_handlerworker~f:(funmessage->Firebug.console##warnmessage;shutdown());{instrumented_computation;shutdown};;