123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2017-2019 The MOPSA Project. *)(* *)(* 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, either version 3 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, see <http://www.gnu.org/licenses/>. *)(* *)(****************************************************************************)(** Hook to profile time spent in analyzing function calls *)openMopsaopenHookopenAstmoduleHook=struct(** {2 Hook header} *)(** *************** *)letname="function_profiler"letdebugfmt=Debug.debug~channel:"function_profiler"fmt(** {2 Command-line options} *)(** ************************ *)(** Path of the output flame graph samples file *)letopt_flame_graph_path=ref""let()=register_builtin_option{key="-flamegraph";category="Profiling";doc=" path where flame graphs samples are saved";spec=Set_string(opt_flame_graph_path,ArgExt.empty);default="";}(** Resolution of the flame graph samples *)letopt_flame_graph_resolution=ref"ms"let()=register_builtin_option{key="-flamegraph-resolution";category="Profiling";doc=" resolution of the flame graph samples";spec=Symbol(["s";"ms";"us";"ns"],(funr->opt_flame_graph_resolution:=r));default="ms";}(** {2 Timing records} *)(** ****************** *)(** Timing record of a function call *)typetiming={callstack:stringlist;(** Call stack *)time:float;(** Time spent in the function *)}(** Collection of past timing records *)letrecords:timingQueue.t=Queue.create()(** Current timing record *)letcur:timingref=ref{callstack=[];time=0.}(** {2 Call stack observer} *)(** *********************** *)(** Update the state when a call is detected *)letcall_detected(call:callsite)=(* First, stop the timer of previous call, save it in the queue
and create a new timing record *)lett=Sys.time()inlettiming={!curwithtime=t-.!cur.time}inQueue.pushtimingrecords;lettiming={callstack=call.call_fun_orig_name::!cur.callstack;time=t;}incur:=timing(** Update the state when a return is detected *)letreturn_detected()=(* Stop the timer of the returning call, save it in the queue and
restore the timing record of the previous call *)lett=Sys.time()inlettiming={!curwithtime=t-.!cur.time}inQueue.pushtimingrecords;lettiming={callstack=List.tl!cur.callstack;time=t;}incur:=timing(** Observe the call stack and update the timing records *)letobserve_callstack(cs:callstack)range=letdepth=callstack_lengthcsin(* Decrease by 1 the current depth to take into account the first
hidden %program call, added by init but not present in the call
stack *)letcur_depth=List.length!cur.callstack-1inifdepth=cur_depththen()elseifdepth=cur_depth+1thencall_detected(callstack_topcs)elseifdepth=cur_depth-1thenreturn_detected()elsedebug"unsupported call stack configuration: current = %d, previous = %d@.current = %a@.previous = %a@."cur_depthdepth(Format.pp_print_listFormat.pp_print_string)!cur.callstackCallstack.pp_callstackcs(** {2 Statistics} *)(** ************** *)(** Print a timing record as a flame graph sample *)letpp_timing_samplefmtt=letresolution=match!opt_flame_graph_resolutionwith|"s"->1.0|"ms"->1000.0|"us"->1000000.0|"ns"->1000000000.0|_->assertfalseinFormat.fprintffmt"%a %d"(Format.pp_print_list~pp_sep:(funfmt()->Format.pp_print_stringfmt";")Format.pp_print_string)(List.revt.callstack)(resolution*.t.time|>int_of_float)(** Export timing records as flame graph samples *)letexport_flame_graph()=leto=open_out!opt_flame_graph_pathinletfmt=Format.formatter_of_out_channeloinQueue.iter(funt->Format.fprintffmt"%a@.%!"pp_timing_samplet)records(** Print the statistics table *)letprint_stats()=letmoduleFunStat=MapExt.StringMapin(* For each function compute:
- The total time
- The self time: time spent in statements of the function
- The number of times the function was called
*)lettotal,self,times,_,longest_fname_length=Queue.fold(fun(total,self,times,last,longest_fname_length)timing->letfname=List.hdtiming.callstackinletlongest_fname_length=ifString.lengthfname>longest_fname_lengththenString.lengthfnameelselongest_fname_lengthinletself=FunStat.addfname(timing.time+.tryFunStat.findfnameselfwithNot_found->0.)selfinlettotal=List.fold_left(funtotalf->FunStat.addf(timing.time+.tryFunStat.findftotalwithNot_found->0.)total)totaltiming.callstackinlettimes=ifList.lengthlast<List.lengthtiming.callstackthenFunStat.addfname(1+tryFunStat.findfnametimeswithNot_found->0)timeselsetimesintotal,self,times,timing.callstack,longest_fname_length)(FunStat.empty,FunStat.empty,FunStat.empty,[],0)recordsin(* Sort functions by the total time *)letsorted=FunStat.bindingstotal|>List.sort(fun(_,total)(_,total')->comparetotal'total)inletopenFormatinprintf"Functions profiling:@.";List.iter(fun(fname,total)->printf"%s%s %.4fs(total) %.4fs(self) x%d@."fname(String.make(longest_fname_length-String.lengthfname)' ')total(FunStat.findfnameself)(FunStat.findfnametimes);)sorted(** {2 Events handlers} *)(** ******************* *)letinitctx=lett=Sys.time()inlettiming={callstack=["%program"];time=t}incur:=timingleton_before_execroutestmtmanflow=observe_callstack(Flow.get_callstackflow)stmt.srangeleton_after_execroutestmtmanflowpost=()leton_before_evalroutesemanticexpmanflow=()leton_after_evalroutesemanticexpmanfloweval=observe_callstack(Cases.get_ctxeval|>find_ctxContext.callstack_ctx_key)exp.erangeleton_finishmanflow=lett=Sys.time()inlettiming={!curwithtime=t-.!cur.time}inQueue.pushtimingrecords;if!opt_flame_graph_path<>""thenexport_flame_graph();print_stats()endlet()=register_stateless_hook(moduleHook)