Module Daemon.Make

Functor for the common parts of all Mavryk daemons: node, baker, endorser and accuser. Handles event handling in particular.

Parameters

module X : PARAMETERS

Signature

exception Terminated_before_event of {
  1. daemon : string;
  2. event : string;
  3. where : string option;
}

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 = {
  1. process : Tezt_wrapper.Process.t;
  2. stdin : Lwt_io.output_channel;
  3. session_state : X.session_state;
  4. mutable event_loop_promise : unit Lwt.t option;
}

When a daemon is running, we store:

  • its process, so that we can terminate it for instance;
  • the event loop promise, which reads events and cleans them up when the daemon terminates;
  • some information about the state of the daemon so that users can query them.

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.

type status =
  1. | Not_running
  2. | Running of session_status

Daemon status

type event_handler =
  1. | Event_handler : {
    1. filter : Tezt_wrapper.JSON.t -> 'a option;
    2. resolver : 'a option Lwt.u;
    } -> event_handler
type event = {
  1. name : string;
  2. value : Tezt_wrapper.JSON.t;
  3. timestamp : float;
}

Raw events.

type t = {
  1. name : string;
  2. color : Tezt_wrapper.Log.Color.t;
  3. path : string;
  4. persistent_state : X.persistent_state;
  5. mutable status : status;
  6. event_pipe : string;
  7. mutable stdout_handlers : (string -> unit) list;
  8. mutable stderr_handlers : (string -> unit) list;
  9. mutable persistent_event_handlers : (event -> unit) list;
  10. 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.

val fresh_name : unit -> string

Generate a fresh indentifier based on X.base_default_name. This function ensures that a same name can't be returned twice.

val get_next_color : unit -> Tezt_wrapper.Log.Color.t

Evaluates in a different color at each call.

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.

val on_event : t -> (event -> unit) -> unit

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.

type observe_memory_consumption =
  1. | Observe of unit -> int option Lwt.t

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.