Current section
Files
Jump to
Current section
Files
lib/extatus.ex
defmodule Extatus do
@moduledoc """
Extatus is an application to report the status of `GenServer` processes to
Prometheus (time series database) via the HTTP endpoint `/metrics`
implemented with `:cowboy`.
The metrics `:telemetry_scrape_duration_seconds` (summary) and
`telemetry_scrape_size_bytes` (summary) are calculated on every request to
the `/metrics` endpoint.
Also for every instrumented `GenServer` process, extatus reports the
metric `:extatus_process_activity` (gauge). This metric indicates that a
process is up (2), down (0) or idle (1) depending on its value.
## Example
To instrument a `GenServer` is necessary to use the `Extatus.Process`
behaviour and implement its callbacks (`gen_name/1` and `report/1`) which
receive the current `GenServer` state as argument. So, for the following
module:
```elixir
defmodule Uninstrumented do
use GenServer
def start_link, do: GenServer.start_link(__MODULE__, nil)
def stop(pid), do: GenServer.stop(pid)
def value(pid), do: GenServer.call(pid, :value)
def inc(pid), do: GenServer.call(pid, :inc)
def dec(pid), do: GenServer.call(pid, :dec)
def init(_), do: {:ok, 0}
def handle_call(:value, _from, n), do: {:reply, {:ok, n}, n}
def handle_call(:inc, _from, n), do: {:reply, :ok, n + 1}
def handle_call(:dec, _from, n), do: {:reply, :ok, n - 1}
def handle_call(_, n), do: {:noreply, n}
end
```
And the instrumented `GenServer` would be the following:
```elixir
defmodule Instrumented do
use GenServer
use Extatus.Process # Extatus.Process behaviour
def start_link, do: GenServer.start_link(__MODULE__, nil)
def stop(pid), do: GenServer.stop(pid)
def value(pid), do: GenServer.call(pid, :value)
def inc(pid), do: GenServer.call(pid, :inc)
def dec(pid), do: GenServer.call(pid, :dec)
# Metric
defmetrics do
gauge :instrument_gauge do
label :label
registry :default
help "Instrument gauge"
end
end
# Name of the process
def get_name(_n), do: {:ok, inspect(self())}
# Report
def report(n) do
{Gauge, spec} = gen_spec(:instrument_gauge, label: "Label")
Gauge.set(spec, n)
end
def init(_) do
{:ok, _} = Extatus.set(__MODULE__, self()) # Add extatus handler.
{:ok, 0}
end
def handle_call(:value, _from, n), do: {:reply, {:ok, n}, n}
def handle_call(:inc, _from, n), do: {:reply, :ok, n + 1}
def handle_call(:dec, _from, n), do: {:reply, :ok, n - 1}
def handle_call(_, n), do: {:noreply, n}
end
```
This `GenServer` will report the current value stored in the server as the
metric `:instrument_gauge` to Prometheus.
## Configuration
The following are the configuration arguments available:
- `:timeout` - Frequency the handlers get the metrics from the processes.
- `:port` - Port where cowboy is listening to request coming from
Prometheus.
- `:prometheus_registry` - Prometheus registry. By default is `:default`.
i.e:
```elixir
config :extatus,
timeout: 5000,
port: 1337,
prometheus_registry: :test
```
"""
use Application
@generator Extatus.Generator
@doc """
Starts a status handler for the provided `module` and `pid`.
"""
@spec set(module, pid) :: Supervisor.on_start_child
def set(module, pid) do
@generator.start_handler(@generator, module, pid)
end
###################
# Application start
@doc false
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = get_children()
opts = [strategy: :one_for_one, name: Extatus.Supervisor]
Supervisor.start_link(children, opts)
end
@doc false
def get_children, do: run_sandbox() |> get_children()
@doc false
def run_sandbox do
Application.get_env(:extatus, :counter_mod) == Extatus.Sandbox.Counter or
Application.get_env(:extatus, :gauge_mod) == Extatus.Sandbox.Gauge or
Application.get_env(:extatus, :histogram_mod) == Extatus.Sandbox.Histogram or
Application.get_env(:extatus, :summary_mod) == Extatus.Sandbox.Summary
end
@doc false
def get_children(true) do
import Supervisor.Spec, warn: false
[
worker(Extatus.Sandbox.Metric, []),
worker(Extatus.Server, [[name: Extatus.Server]]),
supervisor(@generator, [[name: @generator]])
]
end
def get_children(false) do
import Supervisor.Spec, warn: false
[
worker(Extatus.Server, [[name: Extatus.Server]]),
supervisor(@generator, [[name: @generator]])
]
end
end