View Source Existence (Existence v0.3.1)
Health-checks management and access module.
Module provides functions for accessing an overall health-check state and individual dependencies
checks results.
Module is also used to start an Existence process as a part of an application supervision tree.
Existence works by asynchronously spawning user defined dependencies checks functions.
Individual dependencies checks functions results are evaluated to establish an overall
health-check state.
Overall health-check state is healthy only when all user defined dependencies checks are healthy.
It is assumed that healthy state is represented by an :ok atom for both dependencies checks and
for the overall health-check.
Any other result in dependencies checks is associated with an unhealthy dependency check state.
Overall health-check unhealthy state is represented by an :error atom.
User defined dependencies checks functions are spawned as monitored isolated processes. If user dependency check function raises, throws an error, timeouts or fails in any other way it doesn't have a negative impact on other processes and it is gracefully handled by the library.
Current dependencies checks functions results and current overall health-check state are stored
in an ETS table.
Whenever user executes any of available state getters, request is made against ETS table which
has :read_concurrency set to true.
In practice it means that library can handle unlimited numbers of requests per second
without blocking any other processes, adding latency to the response or overloading dependency
with synchronous requests.
Module provides following functions to access checks states:
get_state/1andget_state!/1to get overall health-check state,get_checks/1andget_checks!/1to get dependencies checks states.
Functions with bangs are negligibly cheaper computationally because they don't check if ETS table storing given Existence instance state exists and they will raise if such table doesn't exist.
usage
Usage
After defining dependencies checks options, Existence can be started using
your application supervisor:
# lib/my_app/application.ex
def start(_type, _args) do
health_checks = [
# minimal dependency check configuration:
check_1: %{
mfa: {MyApp.Checks, :check_1, []}
},
# complete dependency check configuration:
check_2: %{
mfa: {MyApp.Checks, :check_2, []},
initial_delay: 1_000,
interval: 30_000,
state: :ok,
timeout: 1_000
}
]
children = [
{Existence, checks: health_checks, state: :ok}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
endWhen Existence is started it has assigned an initial overall health-check state, which
by default is equal to an :error atom, meaning an unhealthy state.
Initial overall health-check state can be changed with a :state key. In a code example above
initial overall health-check state is set to a healthy state with: state: :ok.
Multiple Existence instances can be started by using common Elixir child identifiers
:id and :name, for example:
children = [
{Existence, checks: readiness_checks, id: ExistenceReadiness, name: ReadinessCheck},
{Existence, checks: liveness_checks, name: {:local, LivenessCheck}}
]
configuration
Configuration
Existence options:
:id- term used to identify the child specification internally. Please refer to theSupervisor"Child specification" documentation section for details on child:idkey. Default:Existence.:name- name used to startExistenceprocess locally. If defined as anatom():gen_statem.start_link/3is used to startExistenceprocess without registration. If defined as a{:local, atom()}tuple,:gen_statem.start_link/4is invoked and process is registered locally with a given name. Key value is used to selectExistenceinstance when running any of the state getters, for example:get_state(CustomName). Default:Existence.:checks- keyword list with user defined dependencies checks parameters, see description below for details. Default:[].:state- initial overallExistenceinstance health-check state. Default::error.:on_state_change- MFA tuple pointing at user function which will be synchronously applied on the overall health-check state change. User function should be of two arity. As a first argument it will receive current state as:ok | :erroratom. As a second argument function will receive static arg given in the MFA tuple. Default:nil.
Dependencies checks are defined using a keyword list with configuration parameters defined as a maps, see code example above.
Dependencies checks configuration options:
:mfa-{module, function, arguments}tuple specifying user defined function to spawn when executing given dependency check. Please refer toKernel.spawn_monitor/3documentation for the MFA pattern explanation. Function will be spawned with arguments given in the:mfakey. Required.:initial_delay- amount of time in milliseconds to wait before spawning a dependency check for the first time. Can be used to wait for a dependency process to properly initialize before executing dependency check function first time when application is started. Default:100.:interval- time interval in milliseconds specifying how frequently given check should be executed and dependency checked. Default:30_000.:state- initial dependency check state when startingExistence. Default::error.:timeout- after spawning dependency check function we will wait:timeoutamount of milliseconds for the dependency check function to complete. If dependency check function will do not complete within a given timeout, dependency check function process will be killed, and dependency check state will assume a:killedvalue. Default:5_000.
dependencies-checks
Dependencies checks
User defined dependencies checks functions must return an :ok atom within given :timeout
interval to acquire a healthy state.
Any other values returned by dependencies checks functions are considered as an unhealthy state.
Example health-checks callback functions for two popular dependencies, PostgreSQL and Redis:
#lib/checks.ex
def check_postgres() do
"SELECT 1;"
|> MyApp.Repo.query()
|> case do
{:ok, %Postgrex.Result{num_rows: 1, rows: [[1]]}} -> :ok
_ -> :error
end
end
def check_redis() do
case MyApp.Redix.command(["PING"]) do
{:ok, "PONG"} -> :ok
_ -> :error
end
endPlease notice that dependencies checks functions in the code example above are not wrapped in a
try/1 blocks.
Dependencies checks functions are spawned as monitored processes.
Whenever check function will raise, parent health-check process will be notified with an :info
:DOWN message and dependency check status will be assigned a tuple containing an exception and
a stack trace, for example:
# def check_1(), do: raise("CustomError")
iex> Existence.get_checks()
[
check_1: {%RuntimeError{message: "CustomError"}, [ # ... stack trace ]}
]
iex> Existence.get_state()
:error
Link to this section Summary
Functions
Get dependencies checks states for name instance.
Same as get_checks/1 but raises if name instance doesn't exist.
Get an overall health-check state for name instance.
Same as get_state/1 but raises if name instance doesn't exist.
Link to this section Functions
Get dependencies checks states for name instance.
Function gets current dependencies checks states for an instance started with a given name,
by default Existence.
Dependencies checks functions results are returned as a keyword list.
If no checks were defined function will return an empty list.
Function returns :undefined if name instance doesn't exist.
Dependency check function result equal to an :ok atom means healthy state, any other term is
associated with an unhealthy state.
Example:
iex> Existence.get_checks()
[check_1: :ok, check_2: :ok]iex> Existence.get_checks(NotExisting)
:undefined
Same as get_checks/1 but raises if name instance doesn't exist.
Function will raise with an ArgumentError exception if instance name doesn't exist.
Example:
iex> Existence.get_checks!()
[check_1: :ok, check_2: :ok]iex> Existence.get_checks!(NotExisting)
** (ArgumentError) errors were found at the given arguments:
@spec get_state(name :: atom()) :: :ok | :error | :undefined
Get an overall health-check state for name instance.
Function gets current overall health-check state for an instance started with a given name,
by default Existence.
Function returns an :ok when overall health-check state is healthy and an :error when state
is unhealthy.
Function returns :undefined if name instance doesn't exist.
Overall health-check state is healthy only when all dependencies health checks are healthy.
Example:
iex> Existence.get_state()
:okiex> Existence.get_state(NotExisting)
:undefined
@spec get_state!(name :: atom()) :: :ok | :error
Same as get_state/1 but raises if name instance doesn't exist.
Function will raise with an ArgumentError exception if instance name doesn't exist.
Example:
iex> Existence.get_state!()
:okiex> Existence.get_state!(NotExisting)
** (ArgumentError) errors were found at the given arguments: