123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488(************************************************************************)(* * The Coq Proof Assistant / The Coq Development Team *)(* v * Copyright INRIA, CNRS and contributors *)(* <O___,, * (see version control and CREDITS file for authors & dates) *)(* \VV/ **************************************************************)(* // * This file is distributed under the terms of the *)(* * GNU Lesser General Public License Version 2.1 *)(* * (see LICENSE file for the text of the license) *)(************************************************************************)openUnicodeopenPpopenUtilmoduleM=CString.Map(** [is_profiling] and the profiling info ([stack]) should be synchronized with
the document; the rest of the ref cells are either local to individual
tactic invocations, or global flags, and need not be synchronized, since no
document-level backtracking happens within tactics. We synchronize
is_profiling via an option. *)letis_profiling=Flags.profile_ltacletset_profilingb=is_profiling:=bletget_profiling()=!is_profilingletencountered_invalid_stack_no_self=reffalseletwarn_invalid_stack_no_self=CWarnings.create~name:"profile-invalid-stack-no-self"~category:CWarnings.CoreCategories.ltac(fun()->strbrk"Ltac Profiler encountered an invalid stack (no self \
node). This can happen if you reset the profile during \
tactic execution.")letencounter_invalid_stack_no_self()=ifnot!encountered_invalid_stack_no_selfthenbeginencountered_invalid_stack_no_self:=true;warn_invalid_stack_no_self()end(* *************** tree data structure for profiling ****************** *)typetreenode={name:string;total:float;local:float;ncalls:int;max_total:float;children:treenodeM.t}letempty_treenodename={name;total=0.0;local=0.0;ncalls=0;max_total=0.0;children=M.empty;}letroot="root"letstack=Summary.ref~name:"LtacProf-stack"~local:true[empty_treenoderoot]letreset_profile_tmp()=stack:=[empty_treenoderoot](* ************** XML Serialization ********************* *)letrecof_ltacprof_tactic(name,t)=assert(String.equalnamet.name);letopenXml_datatypeinlettotal=string_of_floatt.totalinletlocal=string_of_floatt.localinletncalls=string_of_intt.ncallsinletmax_total=string_of_floatt.max_totalinletchildren=List.mapof_ltacprof_tactic(M.bindingst.children)inElement("ltacprof_tactic",[("name",name);("total",total);("local",local);("ncalls",ncalls);("max_total",max_total)],children)letof_ltacprof_resultst=letopenXml_datatypeinassert(String.equalt.nameroot);letchildren=List.mapof_ltacprof_tactic(M.bindingst.children)inElement("ltacprof",[("total_time",string_of_floatt.total)],children)letrecto_ltacprof_tacticmxml=letopenXml_datatypeinmatchxmlwith|Element("ltacprof_tactic",[("name",name);("total",total);("local",local);("ncalls",ncalls);("max_total",max_total)],xs)->letnode={name;total=float_of_stringtotal;local=float_of_stringlocal;ncalls=int_of_stringncalls;max_total=float_of_stringmax_total;children=List.fold_leftto_ltacprof_tacticM.emptyxs;}inM.addnamenodem|_->CErrors.anomalyPp.(str"Malformed ltacprof_tactic XML.")letto_ltacprof_resultsxml=letopenXml_datatypeinmatchxmlwith|Element("ltacprof",[("total_time",t)],xs)->{name=root;total=float_of_stringt;ncalls=0;max_total=0.0;local=0.0;children=List.fold_leftto_ltacprof_tacticM.emptyxs}|_->CErrors.anomalyPp.(str"Malformed ltacprof XML.")letfeedback_resultsresults=Feedback.(feedback(Custom(None,"ltacprof_results",of_ltacprof_resultsresults)))(* ************** pretty printing ************************************* *)letformat_secx=(Printf.sprintf"%.3fs"x)letformat_ratiox=(Printf.sprintf"%.1f%%"(100.*.x))letpadlns=ws(max0(n-utf8_lengths))++strsletpadr_withcns=letulength=utf8_lengthsinifInt.equalnulengththenstrselseifn<ulengththenstr(utf8_subs0n)elsestrs++str(String.make(n-ulength)c)letreclist_map_is_lastf=function|[]->[]|[x]->[ftruex]|x::xs->ffalsex::list_map_is_lastfxsletrepeat_strns=ifString.is_emptysthenselseletlen=String.lengthsinString.init(n*len)(funi->s.[imodlen])letheader_name=" tactic"letheader_name_width=utf8_lengthheader_nameletheader_rest="┴──────┴──────┴───────┴─────────┘"letheader_rest_width=utf8_lengthheader_restletheadername_width=str" tactic"++str(String.make(name_width-header_name_width)' ')++str" local total calls max"++fnl()++str(repeat_strname_width"─")++strheader_rest++fnl()moduleLine=structtypet={prefix:string;tac_name:string;local:float;total:float;calls:int;maxtime:float;}letpr~name_widthl=h(padr_with'-'name_width(l.prefix^l.tac_name^" ")++padl7(format_ratiol.local)++padl7(format_ratiol.total)++padl8(string_of_intl.calls)++padl10(format_secl.maxtime))endletreclinearize_node~filterall_totalindentprefix(s,e)={Line.prefix;tac_name=s;local=(e.local/.all_total);total=(e.total/.all_total);calls=e.ncalls;maxtime=e.max_total;}::linearize_table~filterall_totalindentfalsee.childrenandlinearize_table~filterall_totalindentfirst_leveltable=letfold_nl=lets,total=n.name,n.totaliniffilterstotalthen(s,n)::lelselinletls=M.foldfoldtable[]inmatchlswith|[s,n]whennotfirst_level->linearize_node~filterall_totalindent(indent^"└")(s,n)|_->letls=List.sort(fun(_,{total=s1})(_,{total=s2})->compares2s1)lsinletiteris_last=letsep0=iffirst_levelthen""elseifis_lastthen" "else" │"inletsep1=iffirst_levelthen"─"elseifis_lastthen" └─"else" ├─"inlinearize_node~filterall_total(indent^sep0)(indent^sep1)inList.concat(list_map_is_lastiterls)letget_printing_width=ref(fun()->Format.pp_get_marginFormat.std_formatter())letset_get_printing_widthf=get_printing_width:=fletget_printing_width()=!get_printing_width()letprint_table~filterall_totaltable=letlines=linearize_table~filterall_total""truetableinletname_width=List.fold_left(funacc(l:Line.t)->maxacc(utf8_length(l.prefix^l.tac_name)))0linesinletname_width=name_width+1(* +1 for a space at the end *)in(* respect Printing Width unless it's so short that we can't print the header correctly *)letname_width=min(get_printing_width()-header_rest_width)name_widthinletname_width=maxheader_name_widthname_widthinheadername_width++prlist_with_sepfnl(Line.pr~name_width)linesletto_string~filter~cutoffnode=lettree=node.childreninletall_total=M.fold(fun_{total}a->total+.a)node.children0.0inletflat_tree=letglobal=refM.emptyinletfind_tactictnamel=tryM.findtname!globalwithNot_found->lete=empty_treenodetnameinglobal:=M.addtnamee!global;einletadd_tactictnamestats=global:=M.addtnamestats!globalinletsum_statsadd_total{name;total=t1;local=l1;ncalls=n1;max_total=m1}{total=t2;local=l2;ncalls=n2;max_total=m2}={name;total=ifadd_totalthent1+.t2elset1;local=l1+.l2;ncalls=n1+n2;max_total=ifadd_totalthenmaxm1m2elsem1;children=M.empty;}inletreccumulatetable=letiter_({name;children}asstatistics)=iffilternamethenbeginletstats'=find_tacticnameglobalinadd_tacticname(sum_statstruestats'statistics);end;cumulatechildreninM.iteritertableincumulatetree;!globalinletfiltersn=filters&&(all_total<=0.0||n/.all_total>=cutoff/.100.0)inletmsg=h(str"total time: "++padl11(format_sec(all_total)))++fnl()++fnl()++print_table~filterall_totalflat_tree++fnl()++fnl()++print_table~filterall_totaltreeinmsg(* ******************** profiling code ************************************** *)letget_childnamenode=tryM.findnamenode.childrenwithNot_found->empty_treenodenamelettime()=lettimes=Unix.times()intimes.Unix.tms_utime+.times.Unix.tms_stimeletstring_of_callck=lets=string_of_ppcmdsckinlets=String.map(func->ifc='\n'then' 'elsec)sinlets=tryString.subs0(CString.string_index_froms0"(*")withNot_found->sinString.trimsletrecmerge_sub_treenametreeacc=trylett=M.findnameaccinlett={name;total=t.total+.tree.total;ncalls=t.ncalls+tree.ncalls;local=t.local+.tree.local;max_total=maxt.max_totaltree.max_total;children=M.foldmerge_sub_treetree.childrent.children;}inM.addnametaccwithNot_found->M.addnametreeaccletmerge_roots?(disjoint=true)t1t2=assert(String.equalt1.namet2.name);{name=t1.name;ncalls=t1.ncalls+t2.ncalls;local=ifdisjointthent1.local+.t2.localelset1.local;total=ifdisjointthent1.total+.t2.totalelset1.total;max_total=ifdisjointthenmaxt1.max_totalt2.max_totalelset1.max_total;children=M.foldmerge_sub_treet2.childrent1.children}letrecfind_in_stackwhatacc=function|[]->None|{name}asx::restwhenString.equalnamewhat->Some(acc,x,rest)|{name}asx::rest->find_in_stackwhat(x::acc)restletexit_tactic~count_callstart_timename=letdiff=time()-.start_timeinmatch!stackwith|[]|[_]->(* oops, our stack is invalid *)encounter_invalid_stack_no_self();reset_profile_tmp()|node::(parent::restasfull_stack)->ifnot(String.equalnamenode.name)then(* oops, our stack is invalid *)CErrors.anomaly(Pp.strbrk"Ltac Profiler encountered an invalid stack (wrong self node) \
likely due to backtracking into multi-success tactics.");letnode={nodewithtotal=node.total+.diff;local=node.local+.diff;ncalls=node.ncalls+(ifcount_callthen1else0);max_total=maxnode.max_totaldiff;}in(* updating the stack *)letparent=matchfind_in_stacknode.name[]full_stackwith|None->(* no rec-call, we graft the subtree *)letparent={parentwithlocal=parent.local-.diff;children=M.addnode.namenodeparent.children}instack:=parent::rest;parent|Some(to_update,self,rest)->(* we coalesce the rec-call and update the lower stack *)letself=merge_roots~disjoint:falseselfnodeinletupdated_stack=List.fold_left(funsx->(tryM.findx.name(List.hds).childrenwithNot_found->x)::s)(self::rest)to_updateinstack:=updated_stack;List.hd!stackin(* Calls are over, we reset the stack and send back data *)ifrest==[]&&get_profiling()thenbeginassert(String.equalrootparent.name);encountered_invalid_stack_no_self:=false;reset_profile_tmp();feedback_resultsparentend(** [tclWRAPFINALLY before tac finally] runs [before] before each
entry-point of [tac] and passes the result of [before] to
[finally], which is then run at each exit-point of [tac],
regardless of whether it succeeds or fails. Said another way, if
[tac] succeeds, then it behaves as [before >>= fun v -> tac >>= fun
ret -> finally v <*> tclUNIT ret]; otherwise, if [tac] fails with
[e], it behaves as [before >>= fun v -> finally v <*> tclZERO
e]. *)letrectclWRAPFINALLYbeforetacfinally=letopenProofviewinletopenProofview.Notationsinbefore>>=funv->tclCASEtac>>=function|Fail(e,info)->finallyv>>=fun()->tclZERO~infoe|Next(ret,tac')->tclOR(finallyv>>=fun()->tclUNITret)(fune->tclWRAPFINALLYbefore(tac'e)finally)letdo_profile_genpp_callcall_trace?(count_call=true)tac=letopenProofview.Notationsin(* We do an early check to [is_profiling] so that we save the
overhead of [tclWRAPFINALLY] when profiling is not set
*)Proofview.tclLIFT(Proofview.NonLogical.make(fun()->!is_profiling))>>=function|false->tac|true->tclWRAPFINALLY(Proofview.tclLIFT(Proofview.NonLogical.make(fun()->matchpp_callcall_trace,!stackwith|Somec,parent::rest->letname=string_of_callcinletnode=get_childnameparentinstack:=node::parent::rest;Some(name,time())|Some_,[]->assertfalse|_->None)))tac(function|Some(name,start_time)->(Proofview.tclLIFT(Proofview.NonLogical.make(fun()->exit_tactic~count_callstart_timename)))|None->Proofview.tclUNIT())(* ************** Accumulation of data from workers ************************* *)letget_local_profiling_results()=List.hd!stack(* We maintain our own cache of document data, given that the
semantics of the STM implies that synchronized state for opaque
proofs will be lost on QED. This provides some complications later
on as we will have to simulate going back on the document on our
own. *)moduleDData=structtypet=Feedback.doc_id*Stateid.tletcomparexy=comparexyendmoduleSM=Map.Make(DData)letdata=refSM.emptylet_=Feedback.(add_feeder(function|{doc_id=d;span_id=s;contents=Custom(_,"ltacprof_results",xml)}->letresults=to_ltacprof_resultsxmlinletother_results=(* Multi success can cause this *)trySM.find(d,s)!datawithNot_found->empty_treenoderootindata:=SM.add(d,s)(merge_rootsresultsother_results)!data|_->()))letreset_profile()=encountered_invalid_stack_no_self:=false;reset_profile_tmp();data:=SM.empty(* ****************************** Named timers ****************************** *)lettimer_data=refM.emptylettimer_name=function|Somev->v|None->""letrestart_timername=timer_data:=M.add(timer_namename)(System.get_time())!timer_dataletget_timername=tryM.find(timer_namename)!timer_datawithNot_found->System.get_time()letfinish_timing~prefixname=lettend=System.get_time()inlettstart=get_timernameinFeedback.msg_notice(strprefix++pr_optstrname++str" ran for "++System.fmt_time_differencetstarttend)(* ******************** *)letprint_results_filter~cutoff~filter=data:=SM.filter(fun(doc,id)_->Stateid.is_valid~docid)!data;letresults=SM.fold(fun_->merge_roots~disjoint:true)!data(empty_treenoderoot)inletresults=merge_rootsresults(CList.last!stack)inFeedback.msg_notice(to_string~cutoff~filterresults);;letprint_results~cutoff=print_results_filter~cutoff~filter:(fun_->true)letprint_results_tactictactic=print_results_filter~cutoff:!Flags.profile_ltac_cutoff~filter:(funs->String.(equaltactic(sub(s^".")0(min(1+lengths)(lengthtactic)))))letdo_print_results_at_close()=ifget_profiling()thenprint_results~cutoff:!Flags.profile_ltac_cutofflet()=letopenGoptionsindeclare_bool_option{optstage=Summary.Stage.Interp;optdepr=None;optkey=["Ltac";"Profiling"];optread=get_profiling;optwrite=set_profiling}