Mavkit_smart_rollup_wasm_debugger_lib.Profilingmodule Vector = Mavryk_lazy_containers.Lazy_vector.Int32VectorCall stack representation and construction.
The call stack computation algorithm is the following:
There are two components: the current node (or stack frame) and the continuation (a list of stack frames). There's a "toplevel node" describing the execution at the toplevel of the interpreter. A node contains:
id: a function call representation (an identifier)t: the ticks elapsed during the calltime: the time elapsed during the callsub: the subcalls.Note that for the rest of the algorithm, `time` will be eluded as its computation is equivalent to the ticks.
The algorithm starts with an empty toplevel and an empty continuation.
Let's take an example: call: f () { ...... g () { .... h () { ...... } .......... } ......... } tick: 0 ----------> 10 -------> 30 ---------> 60 --------> 100 -----> 160 10 ticks 20 ticks 30 ticks 40 ticks 60 ticks
T (nodes) : toplevel K : continuation (list) N : current node (N(id) means it hasn't changed from previous step)
N, K |- exec
Start: T, |- f () { g () { h () { } } } ==> at tick 0 N (f, 0, ), T |- g () { h () { } } } ==> at tick 10 N (g, 10, ), N (f, 10 - 0 = 10, []); T |- h () { } } } ==> at tick 30 N (h, 30, ), N (g, 30 - 10 = 20, []); N(f); T |- } } } ==> at tick 60 N (g, 60 - 20 = 40, N (h, 60 - 30 = 30, [])), N(f); T |- } } ==> at tick 100 N (f, 100 - 10 = 90, N (g, 100 - 40 = 60, [N(h)])), T |- } ==> at tick 160 T N (f, 160 - 90 = 70, [N(g, 60, [N(h, 30, [])])]), |- _
type 'function_call call_stack = | Node of 'function_call
* Z.t
* Ptime.span option
* 'function_call call_stack list| Toplevel of 'function_call call_stack listval fold_call_stack :
('a -> 'b -> Z.t -> Ptime.span option -> 'c) ->
'd ->
'e call_stack ->
'dval end_function_call :
Z.t ->
(unit -> Ptime.span option) ->
'a call_stack ->
'b call_stack list ->
'a call_stack * 'b call_stack listend_function_call current_tick current_function call_stack implements an ending call. Please refer to the prelude of the file.
val call_function :
'a ->
Z.t ->
(unit -> Ptime.span option) ->
'b call_stack ->
'c call_stack list ->
'd call_stack * 'c call_stack listcall_function called_function current_tick current_function call_stack implements a function start. Please refere to the prelude of the module.
Profiling the execution of the PVM
A function call can be either a direct call, a call through a reference or an internal step of the PVM.
val pp_call : Stdlib.Format.formatter -> function_call -> unitval initial_eval_call : function_callinitial_eval_call is `kernel_run` function call.
val update_on_decode :
Z.t ->
(unit -> Ptime.span option) ->
('a call_stack * 'a call_stack list) ->
Mavryk_webassembly_interpreter.Decode.module_kont ->
(function_call call_stack * 'a call_stack list) option Lwt.tupdate_on_decode current_tick current_call_state starts and stop `internal` calls related to the Decode step of the PVM.
val update_on_link :
Z.t ->
(unit -> Ptime.span option) ->
('a call_stack * 'a call_stack list) ->
Mavryk_webassembly_interpreter.Ast.module_'
Mavryk_webassembly_interpreter.Source.phrase ->
Vector.key ->
(function_call call_stack * 'a call_stack list) option Lwt.tupdate_on_link current_tick current_call_state starts and stop `internal` call to the Link step of the PVM.
val update_on_init :
Z.t ->
(unit -> Ptime.span option) ->
('a call_stack * 'a call_stack list) ->
Mavryk_webassembly_interpreter.Eval.init_kont ->
(function_call call_stack * 'a call_stack list) option Lwt.tupdate_on_init current_tick current_call_state starts and stop `internal` call to the Init step of the PVM.
val update_on_instr :
Z.t ->
(unit -> Ptime.span option) ->
'a call_stack ->
'a call_stack list ->
string Custom_section.FuncMap.t ->
Mavryk_webassembly_interpreter.Eval.admin_instr' ->
(function_call call_stack * 'a call_stack list) option Lwt.tupdate_on_instr current_tick current_node call_stack handle function calls during the evaluation.
val update_on_eval :
Z.t ->
(unit -> Ptime.span option) ->
(function_call call_stack * function_call call_stack list) ->
string Custom_section.FuncMap.t ->
Mavryk_webassembly_interpreter.Eval.step_kont ->
(function_call call_stack * function_call call_stack list) option Lwt.tupdate_on_eval current_tick current_call_state handle function calls and end during the evaluation.
val update_call_stack :
Z.t ->
(unit -> Ptime.span option) ->
(function_call call_stack * function_call call_stack list) ->
string Custom_section.FuncMap.t ->
Mavryk_scoru_wasm.Wasm_pvm_state.Internal_state.tick_state ->
(function_call call_stack * function_call call_stack list) option Lwt.tupdate_call_stack current_tick current_state_call symbols state returns the call state changes for any state. Returns None if no change happened.
module State : sig ... endmodule Make
(Wasm_utils : Mavryk_scoru_wasm_helpers.Wasm_utils_intf.S) :
sig ... endFlamegraph building
Flamegraph are an aggregation of all the same callstacks, thus there is no longer a notion of time. We can easily collapse all nodes into a single one.
module StringMap : sig ... endval collapse_stack :
max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
'b call_stack ->
(StringMap.key * Z.t) listcollapse_stack ~max_depth pp_call call_stack collapses a call stack into a valid flamegraph. Node deeper than max_depth are not considered. pp_call is used to print the identifiers.
Pretty printing and flamegraph output
pp_indent ppf depth prints an indentation corresponding to the given depth.
val pp_nodes :
?max_depth:int ->
int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'b call_stack list ->
unitval pp_stack :
?max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'a call_stack ->
unitpp_stack ~max_depth ppf stack pretty prints the stack. It should be used for debug only.
val pp_flame_callstack_node :
prefix:string ->
depth:int ->
max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'b call_stack ->
unitval pp_flame_callstack_nodes :
prefix:string ->
depth:int ->
max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'b call_stack list ->
unitval pp_callstack_as_flamegraph :
max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'a call_stack ->
unitpp_callstack_as_flamegraph if pp_stack with the syntax of flamegraphs.
val pp_collapsed_flamegraph :
max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'a call_stack ->
unitpp_flamegraph collapses the stack and print it as a valid flamegraph.
val pp_flamegraph :
collapse:bool ->
max_depth:int ->
(Stdlib.Format.formatter -> 'a -> unit) ->
Stdlib.Format.formatter ->
'a call_stack ->
unitpp_flamegraph ~collapsed ~max_depth pp_call ppf call_stack outputs the given call_stack with its `flamegraph` representation. If collapse =
true, the stacks are collapsed. This can be useful to output smaller files, but the stack cannot be analyzed on a time basis (i.e. as a flamechart).
val aggregate_toplevel_time_and_ticks :
'a call_stack ->
('b * Z.t * Ptime.span option) listaggregate_toplevel_time_and_ticks ~call_stack counts the time and ticks spent in each toplevel phases during an execution.
val pp_ticks_and_time :
Stdlib.Format.formatter ->
(function_call * Z.t * Ptime.span option) ->
unit