defmodule TelemetryMetricsMnesia do
@moduledoc """
`Telemetry.Metrics` reporter and metrics backend based on Mnesia DB.
## Installation
Just add the reporter to your dependencies in `mix.exs`:
defp deps do
[
{:telemetry_metrics_mnesia, "~> 0.1.0"}
]
end
## Starting
To use it, start the reporter with the `start_link/1` function, providing it a list of
`Telemetry.Metrics` metric definitions:
<!-- tabs-open -->
### From code
```elixir
import Telemetry.Metrics
TelemetryMetricsMnesia.start_link(
metrics: [
counter("http.request.count"),
sum("http.request.payload_size"),
last_value("vm.memory.total")
]
)
```
### From supervisor
```elixir
import Telemetry.Metrics
children = [
{TelemetryMetricsMnesia, [
metrics: [
counter("http.request.count"),
sum("http.request.payload_size"),
last_value("vm.memory.total")
]
]}
]
Supervisor.start_link(children, ...)
```
<!-- tabs-close -->
## How metrics stored
By default, raw events with timestamps are stored in `memory_only` tables in Mnesia DB without distribution.
These options are going to be implemented soon...
## How metrics returned
### Single metric
A `Map` with a metric type key.
#### `Counter`
```
%{"Counter" => 2}
```
#### `Sum`
```
%{"Sum" => 4}
```
#### `last_value`
```
%{"LastValue" => 8}
```
#### `Distribution`
```
%{"Distribution" => %{
median: 5,
p75: 6,
p90: 6.5,
p95: 6.6,
p99: 6.6
}
}
```
#### `Summary`
```
%{"Summary" => %{
mean: 5,
median: 6,
variance: 1,
standard_diviation: 0.5,
count: 100
}
}
```
### Several metric types at once
A `Map` with several metric type keys will be returned.
```elixir
%{
"Distribution" => %{
median: 4,
p75: 5,
p90: 7,
p95: 7,
p99: 8
},
"Summary" => %{
mean: 5,
variance: 1,
standard_deviation: 0.5,
median: 5,
count: 100
}
}
```
### Metric with tags
A nested `Map` with metric type keys at the first level and maps with tags vs values at the second.
```elixir
%{
"Counter" => %{
%{endpoint: "/", code: 200} => 10,
%{endpoint: "/", code: 500} => 100,
%{endpoint: "/api", code: 200} => 500
}
}
```
"""
use GenServer
alias Telemetry.Metrics
alias TelemetryMetricsMnesia.{Db, EventHandler}
@type options() :: [option()]
@type option() :: {:metrics, Telemetry.Metrics.t()}
@typedoc """
See ["How metrics returned"](#module-how-metrics-returned)
"""
@type metric_data() :: %{String.t() => number() | map() | term()}
@doc """
Starts a reporter and links it to the process.
Available options:
- `:metrics` - Required. List of metrics to handle.
More examples in ["Starting"](#module-starting)
"""
@spec start_link(options()) :: GenServer.on_start()
def start_link(options), do: GenServer.start_link(__MODULE__, options, name: __MODULE__)
@impl true
def init(options) do
Process.flag(:trap_exit, true)
Db.init()
metrics = options[:metrics]
handler_ids = EventHandler.attach(metrics)
{:ok, %{metrics: metrics, handler_ids: handler_ids}}
end
@doc """
Retrieves metric data by its name. Returns a map.
`opts` are reserved for future.
More info in ["How metrics returned"](#module-how-metrics-returned).
"""
@spec fetch(Metrics.metric_name(), %{}) :: metric_data()
def fetch(metric_name, opts \\ %{}), do: GenServer.call(__MODULE__, {:fetch, metric_name, opts})
@impl true
def handle_call({:fetch, metric_name, _opts}, _from, %{metrics: metrics} = state) do
reply =
for %mod{} = metric <- metrics, metric.name == metric_name, into: %{} do
type =
mod
|> Module.split()
|> List.last()
{type, Db.fetch(metric)}
end
{:reply, reply, state}
end
@impl true
def terminate(_reason, state) do
EventHandler.detach(state.handler_ids)
:ok
end
end