Daemon.Make
Functor for the common parts of all Mavryk daemons: node, baker, endorser and accuser. Handles event handling in particular.
module X : PARAMETERS
Exception raised by wait_for
functions if the daemon terminates before the event.
You may catch or let it propagate to cause the test to fail. daemon
is the name of the daemon. event
is the name of the event. where
is an additional optional constraint, such as "level >= 10"
.
type session_status = {
process : Tezt_wrapper.Process.t;
stdin : Lwt_io.output_channel;
session_state : X.session_state;
mutable event_loop_promise : unit Lwt.t option;
}
When a daemon is running, we store:
The event loop promise is particularly important as when we terminate the daemon we must also wait for the event loop to finish cleaning up before we start the daemon again. The event loop is also responsible to set the status of the daemon to Not_running
, which is another reason to wait for it to finish before restarting a daemon. Otherwise we could have a Not_running
daemon which would be actually running.
Daemon status
type t = {
name : string;
color : Tezt_wrapper.Log.Color.t;
path : string;
persistent_state : X.persistent_state;
mutable status : status;
event_pipe : string;
mutable stdout_handlers : (string -> unit) list;
mutable stderr_handlers : (string -> unit) list;
mutable persistent_event_handlers : (event -> unit) list;
mutable one_shot_event_handlers : event_handler list
Tezt_wrapper.Base.String_map.t;
}
Daemon states.
val name : t -> string
Get the name of a daemon.
val terminate : ?timeout:float -> t -> unit Lwt.t
Send SIGTERM to a daemon and wait for it to terminate.
Default timeout
is 30 seconds, after which SIGKILL is sent.
val kill : t -> unit Lwt.t
Send SIGKILL to a daemon and wait for it to terminate.
val stop : t -> unit Lwt.t
Send SIGSTOP to a daemon.
val continue : t -> unit Lwt.t
Send SIGCONT to a daemon.
Generate a fresh indentifier based on X.base_default_name
. This function ensures that a same name can't be returned twice.
val create :
path:string ->
?runner:Tezt_wrapper.Runner.t ->
?name:string ->
?color:Tezt_wrapper.Log.Color.t ->
?event_pipe:string ->
X.persistent_state ->
t
Create a daemon.
The standard output and standard error output of the daemon will be logged with prefix name
and color color
.
Default event_pipe
is a temporary file whose name is derived from name
. It will be created as a named pipe so that daemon events can be received.
If runner
is specified, the daemon will be spawned on this runner using SSH.
val get_event_from_full_event : Tezt_wrapper.JSON.t -> event option
Takes the given JSON full event of the following form and evaluates in an event using <name>
and <value>
:
{
"fd-sink-item.v0": {
[...]
"event": { <name>:<value> }
}
}
If the given JSON does not match the right structure, and in particular if the value of the field "event"
is not a one-field object, the function evaluates in None.
val run :
?env:string Tezt_wrapper.Base.String_map.t ->
?runner:Tezt_wrapper.Runner.t ->
?on_terminate:(Unix.process_status -> unit Lwt.t) ->
?event_level:Level.default_level ->
?event_sections_levels:(string * Level.level) list ->
?capture_stderr:bool ->
t ->
X.session_state ->
string list ->
unit Lwt.t
Spawn a daemon.
If capture_stderr
is true
(default to false
), then functions like Process
.check_and_read_stderr or Process
.check_error will not work as expected with the process of the daemon (as stored in its session_status
).
val wait_for_full :
?where:string ->
t ->
string ->
(Tezt_wrapper.JSON.t -> 'a option) ->
'a Lwt.t
Wait for a custom event to occur.
Usage: wait_for_full daemon name filter
If an event named name
occurs, apply filter
to its whole json, which is of the form:
{
"fd-sink-item.v0": {
"hostname": "...",
"time_stamp": ...,
"section": [ ... ],
"event": { <name>: ... }
}
}
If filter
returns None
, continue waiting. If filter
returns Some x
, return x
.
where
is used as the where
field of the Terminated_before_event
exception if the daemon terminates. It should describe the constraint that filter
applies, such as "field level exists"
.
It is advised to register such event handlers before starting the daemon, as if they occur before being registered, they will not trigger your handler. For instance, you can define a promise with let x_event = wait_for daemon "x" (fun x -> Some x)
and bind it later with let* x = x_event
.
val wait_for :
?where:string ->
t ->
string ->
(Tezt_wrapper.JSON.t -> 'a option) ->
'a Lwt.t
Same as wait_for_full
but ignore metadata from the file descriptor sink.
More precisely, filter
is applied to the value of field "fd-sink-item.v0"."event".<name>
.
If the daemon receives a JSON value that does not match the right JSON structure, it is not given to filter
and the event is ignored. See wait_for_full
to know what the JSON value must look like.
Add a callback to be called whenever the daemon emits an event.
Contrary to wait_for
functions, this callback is never removed.
Listening to events with on_event
will not prevent wait_for
promises to be fulfilled. You can also have multiple on_event
handlers, although the order in which they trigger is unspecified.
val on_stdout : t -> (string -> unit) -> unit
Add a callback to be called whenever the daemon prints to its stdout.
Contrary to wait_for
functions, this callback is never removed.
Listening to events with on_stdout
will not prevent wait_for
promises to be fulfilled. You can also have multiple on_stdout
handlers, although the order in which they trigger is unspecified.
val on_stderr : t -> (string -> unit) -> unit
Add a callback to be called whenever the daemon prints to its stderr. run
must have been called with capture_stderr
flag set to true, to call callbacks registered this way.
Contrary to wait_for
functions, this callback is never removed.
Listening to events with on_stderr
will not prevent wait_for
promises to be fulfilled. You can also have multiple on_stderr
handlers, although the order in which they trigger is unspecified.
val log_events : ?max_length:int -> t -> unit
Register an event handler that logs all events.
Use this when you need to debug or reverse engineer incoming events. Usually you do not want to keep that in the final versions of your tests.
The max_length
optional parameter can be used to limit the length of the output of each event; outputs longer than the limit are truncated at the limit and "...
" is appended to them to mark the truncation.
Values returned by memory_consumption
.
val memory_consumption : t -> observe_memory_consumption Lwt.t
Observe memory consumption of the daemon.
This function requires perf
and heaptrack
in the PATH and kernel.perf_event_paranoid
to be permissive enough. Otherwise, the observation will always return None
.
The returned function gives the peak of memory consumption observed since the observation has started.
memory_consumption daemon
starts the observation and returns Some (Observe get)
. get ()
stops the observation and returns the observation memory consumption.