Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file ecaml_profile.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337open!Core_kernelopen!Async_kernelopen!Importinclude(valMajor_mode.define_derived_mode("ecaml-profile-mode"|>Symbol.intern)[%here]~docstring:{|
The major mode for the *profile* buffer, which holds a log of Ecaml profile output.
|}~define_keys:["SPC","scroll-up"|>Symbol.intern;"DEL","scroll-down"|>Symbol.intern;"<","beginning-of-buffer"|>Symbol.intern;">","end-of-buffer"|>Symbol.intern;"q","quit-window"|>Symbol.intern]~mode_line:"ecaml-profile"~initialize:(ReturnsValue.Type.unit,fun()->Minor_mode.enableMinor_mode.read_only)())let()=Keymap.suppress_keymap(Major_mode.keymapmajor_mode)~suppress_digits:truemoduleStart_location=structincludeProfile.Start_locationletto_stringt=[%sexp(t:t)]|>Sexp.to_string|>String.lowercase|>String.tr~target:'_'~replacement:'-';;letto_symbolt=t|>to_string|>Symbol.internletdocstring=function|End_of_profile_first_line->"put the time at the end of the profile's first line"|Line_preceding_profile->"put the time on its own line, before the profile";;endmoduleProfile_buffer:sigvalprofile_buffer:unit->Buffer.toptionend=structtypet=|Absent|Initializing|ThisofBuffer.t[@@derivingsexp_of]letprofile_buffer:tref=refAbsentletinitialize_profile_buffer()=profile_buffer:=Initializing;letbuffer=Buffer.create~name:"*profile*"inBackground.don't_wait_for[%here](fun()->let%bind()=Major_mode.change_tomajor_mode~in_:bufferinBuffer_local.setCurrent_buffer.truncate_linestruebuffer;profile_buffer:=Thisbuffer;return());;letprofile_buffer()=match!profile_bufferwith|Initializing->None|Absent->initialize_profile_buffer();None|Thisbuffer->ifBuffer.is_livebufferthenSomebufferelse(initialize_profile_buffer();None);;endlettag_function=Defvar.defvar("ecaml-profile-tag-frame-function"|>Symbol.intern)[%here]~docstring:{|
If non-nil, ecaml-profile calls this function with 0 arguments when creating a profile
frame. The output is added to the profile frame. |}~type_:(Value.Type.nil_orFunction.type_)~initial_value:None();;(* Store as strings because we only need them as strings for completion anyway. *)letprofiled_elisp_functions=Hash_set.create(moduleString)letelisp_function_wrapper_namefn="ecaml-profile-wrapper-for-"^Symbol.namefn|>Symbol.intern;;letinitialize()=let(_:_Customization.t)=Customization.defcustom_enum("ecaml-profile-start-location"|>Symbol.intern)[%here](moduleStart_location)~docstring:{| Where to render a profile's start time: |}~group:Customization.Group.ecaml~standard_value:End_of_profile_first_line~on_set:(funstart_location->Profile.start_location:=start_location)()inletshould_profile=Customization.defcustom("ecaml-profile-should-profile"|>Symbol.intern)[%here]~docstring:{| Whether profiling is enabled. |}~group:Customization.Group.ecaml~type_:Value.Type.bool~customization_type:Boolean~standard_value:false~on_set:(funbool->Profile.should_profile:=bool)()inlet_hide_if_less_than=Customization.defcustom("ecaml-profile-hide-frame-if-less-than"|>Symbol.intern)[%here]~docstring:{| Hide profile frames shorter than this duration. |}~group:Customization.Group.ecaml~type_:Value.Type.string~customization_type:String~standard_value:"1ms"~on_set:(funstring->Profile.hide_if_less_than:=string|>Time_ns.Span.of_string)()inlet_hide_top_level_if_less_than=Customization.defcustom("ecaml-profile-hide-top-level-if-less-than"|>Symbol.intern)[%here]~docstring:{| Hide profiles shorter than this duration. |}~group:Customization.Group.ecaml~type_:Value.Type.string~customization_type:String~standard_value:"100ms"~on_set:(funstring->Profile.hide_top_level_if_less_than:=string|>Time_ns.Span.of_string)()inifSystem.is_interactive()then(* We start initializing the profile buffer, so that it exists when we need it. *)ignore(Profile_buffer.profile_buffer():Buffer.toption);(Profile.sexp_of_time_ns:=funtime_ns->match[%sexp(time_ns:Core.Time_ns.t)]with|List[date;ofday]->List[ofday;date]|sexp->sexp);(Profile.output_profile:=funstring->(* If [output_profile] raises, then Nested_profile use [eprint_s] to print the
exception, which doesn't work well in Emacs. So we do our own exception
handling. *)trymatchProfile_buffer.profile_buffer()with|None->()|Somebuffer->Current_buffer.set_temporarilySyncbuffer~f:(fun()->Current_buffer.inhibit_read_onlySync(fun()->Current_buffer.append_tostring))with|exn->message_s[%message"unable to output profile"~_:(exn:exn)]);Profile.tag_frames_with:=Some(fun()->matchCurrent_buffer.value_exntag_functionwith|None->None|Somef->Some(f|>Function.to_value|>Value.funcall0|>[%sexp_of:Value.t]));Hook.addElisp_gc.post_gc_hook(Hook.Function.create("ecaml-profile-record-gc"|>Symbol.intern)[%here](* We don't profile this hook so that the gc frame is attributed to the enclosing
frame that actually experienced the gc. *)~should_profile:false~hook_type:Normal(ReturnsValue.Type.unit)(letlast_gc_elapsed=ref(Elisp_gc.gc_elapsed())infun()->if!Profile.should_profilethen(letmoduleClock=Profile.Private.Clockinletclock=!Profile.Private.clockinletgc_elapsed=Elisp_gc.gc_elapsed()inletgc_took=ifnotam_running_testthenTime_ns.Span.(-)gc_elapsed!last_gc_elapsedelse((* A fixed time, to make test output deterministic. *)lettook=10|>Time_ns.Span.of_int_msinClock.advanceclock~by:took;took)inlast_gc_elapsed:=gc_elapsed;letstop=Clock.nowclockinletgcs_done=Ref.set_temporarilyProfile.should_profilefalse~f:Elisp_gc.gcs_doneinProfile.Private.record_frame~start:(Time_ns.substopgc_took)~stop~message:(lazy[%message"gc"(gcs_done:intopaque_in_test)]))));letset_should_profilebool=Customization.set_valueshould_profilebool;letverb=if!Profile.should_profilethen"enabled"else"disabled"inmessage(String.concat["You just ";verb;" ecaml profiling"])inDefun.defun_nullary_nil("ecaml-toggle-profiling"|>Symbol.intern)[%here]~docstring:"Enable or disable logging of elisp->ecaml calls and durations in the `*profile*' \
buffer."~interactive:No_arg(fun()->set_should_profile(not!Profile.should_profile));Defun.defun_nullary_nil("ecaml-enable-profiling"|>Symbol.intern)[%here]~docstring:"Enable logging of elisp->ecaml calls and durations in the `*profile*' buffer."~interactive:No_arg(fun()->set_should_profiletrue);Defun.defun_nullary_nil("ecaml-disable-profiling"|>Symbol.intern)[%here]~docstring:"Disable logging of elisp->ecaml calls and durations in the `*profile*' buffer."~interactive:No_arg(fun()->set_should_profilefalse);Defun.defun("ecaml-profile-inner"|>Symbol.intern)[%here]~docstring:"profile an elisp function using Nested_profile"~should_profile:false(ReturnsValue.Type.value)(let%map_open.Defun()=return()anddescription=required"description"stringandf=required"function"valueinprofileSync(lazy[%messagedescription])(fun()->Value.funcall0f));Defun.defun("ecaml-profile-elisp-function"|>Symbol.intern)[%here]~docstring:"Wrap the given function in a call to [Nested_profile.profile]."~interactive:(lethistory=Minibuffer.History.find_or_create("ecaml-profile-elisp-function-history"|>Symbol.intern)[%here]inArgs(fun()->let%bindfunction_name=Completing.read_function_name~prompt:"Profile function: "~historyinreturn[function_name|>Value.intern]))(ReturnsValue.Type.unit)(let%map_open.Defun()=return()andfn=required"function"Symbol.tinAdvice.around_values(elisp_function_wrapper_namefn)[%here]~for_function:fn~should_profile:falseSync(funfargs->profileSync(lazy[%sexp((fn|>Symbol.to_value)::args:Value.tlist)])(fun()->fargs));Hash_set.addprofiled_elisp_functions(Symbol.namefn);message(concat["You just added Ecaml profiling of [";fn|>Symbol.name;"]"]));Defun.defun("ecaml-unprofile-elisp-function"|>Symbol.intern)[%here]~docstring:"Remove the profiling wrapper from the given function."~interactive:(lethistory=Minibuffer.History.find_or_create("ecaml-unprofile-elisp-function-history"|>Symbol.intern)[%here]inArgs(fun()->let%bindfunction_name=Completing.read()~prompt:"Unprofile function: "~history~collection:(This(Hash_set.to_listprofiled_elisp_functions))~require_match:Trueinreturn[function_name|>Value.intern]))(ReturnsValue.Type.unit)(let%map_open.Defun()=return()andfn=required"function"Symbol.tinAdvice.remove(elisp_function_wrapper_namefn)~for_function:fn;Hash_set.removeprofiled_elisp_functions(Symbol.namefn);message(concat["You just removed Ecaml profiling of [";fn|>Symbol.name;"]"]));Defun.defun_nullary("ecaml-profile-test-parallel-profile"|>Symbol.intern)[%here](Returns_deferredValue.Type.unit)(fun()->profileAsync(lazy[%sexp"The whole thing"])(fun()->let%bind()=profileAsync(lazy[%sexp"branch1"])(fun()->Async.Clock.after(Time.Span.of_sec1.))and()=profileAsync(lazy[%sexp"branch2"])(fun()->Async.Clock.after(Time.Span.of_sec0.8))inreturn()));;modulePrivate=structlettag_function=tag_functionend