Packages

Exmetrics provides counters, gauges and histograms for instrumenting an elixir application.

Current section

Files

Jump to
exmetrics lib metrics.ex
Raw

lib/metrics.ex

defmodule Exmetrics do
use Application
require Logger
@doc """
Returns all registered metrics.
iex> Exmetrics.Counter.incr("sample_counter")
iex> Exmetrics.Gauge.set("sample_gauge", 1)
iex> snapshot = Exmetrics.snapshot
iex> get_in(snapshot, [:counters, "sample_counter"])
1
iex> get_in(snapshot, [:gauges, "sample_gauge"])
1
"""
@spec snapshot() :: map()
def snapshot do
Exmetrics.Worker.state
|> update_in([:gauges], &realize_gauge/1)
|> update_in([:histograms], &merge_all_histograms/1)
end
defp realize_gauge(gauge) when is_function(gauge), do: apply_no_args(gauge)
defp realize_gauge(gauge_map) when is_map(gauge_map) do
gauge_map
|> Enum.reduce(%{}, fn {key, func}, acc ->
Map.put(acc, key, apply_no_args(func))
end)
end
defp merge_all_histograms(histograms) do
histograms
|> Enum.map(&merge_histogram_windows/1)
end
defp merge_histogram_windows({_, histogram} = data) when is_nil(histogram) do
data
end
defp merge_histogram_windows({_, %{merged: merged, histograms: windows} = histogram}) do
:hdr_histogram.reset(merged)
windows
|> Enum.each(fn src ->
:hdr_histogram.add(merged, src)
end)
%{histogram | merged: merged}
end
defp apply_no_args(func) when is_nil(func) do
nil
end
defp apply_no_args(func) do
apply(func, [])
end
defmodule Gauge do
@moduledoc """
Gauges measure numeric values of a metric at the current point in time.
e.g. "currently active threads"
"""
@doc ~S"""
Set a gauge to return the value of a lazy evaluated function.
iex> Exmetrics.Gauge.set "foo_fn", fn -> 1 end
:ok
iex> Exmetrics.Gauge.get "foo_fn"
1
Set a gauge to a certain value.
iex> Exmetrics.Gauge.set "foo", 1
:ok
iex> Exmetrics.Gauge.get "foo"
1
"""
@spec set(String.t, function) :: atom
def set(name, func) when is_function(func) do
Exmetrics.Worker.set_gauge(name, func)
end
@spec set(String.t, integer) :: atom
def set(name, value) do
Exmetrics.Worker.set_gauge(name, fn -> value end)
end
@doc ~S"""
Get the value of a gauge.
iex> Exmetrics.Gauge.set "bar", 10
:ok
iex> Exmetrics.Gauge.get "bar"
10
iex> Exmetrics.Gauge.get "doesnt_exist"
nil
"""
@spec get(String.t) :: (integer | nil)
def get(name) do
Exmetrics.Worker.get_gauge(name)
end
@doc ~S"""
Remove a gauge from the metrics collection.
iex> Exmetrics.Gauge.set "to_be_removed", 10
:ok
iex> Exmetrics.Gauge.get "to_be_removed"
10
iex> Exmetrics.Gauge.remove "to_be_removed"
:ok
iex> Exmetrics.Gauge.get "to_be_removed"
nil
"""
@spec remove(String.t) :: atom
def remove(name) do
Exmetrics.Worker.remove_gauge(name)
end
end
defmodule Counter do
@moduledoc """
Counters represent integer values that increase monotonically.
"""
@doc ~S"""
Increments the counter 'name' by 1.
iex> Exmetrics.Counter.incr "foo"
:ok
iex> Exmetrics.Counter.get "foo"
1
"""
@spec incr(String.t) :: atom
def incr(name) do
Exmetrics.Worker.increment_counter(name, 1)
end
@doc ~S"""
Increments the counter 'name' by n.
iex> Exmetrics.Counter.add "bar", 5
:ok
iex> Exmetrics.Counter.get "bar"
5
"""
@spec add(String.t, integer) :: atom
def add(name, n) when is_integer(n) do
Exmetrics.Worker.increment_counter(name, n)
end
@doc ~S"""
Gets the value of a counter.
iex> Exmetrics.Counter.add "baz", 42
:ok
iex> Exmetrics.Counter.get "baz"
42
iex> Exmetrics.Counter.get "doesnt_exist"
nil
"""
@spec get(String.t) :: integer | nil
def get(name) do
Exmetrics.Worker.get_counter(name)
end
@doc ~S"""
Reset counter 'name' to 0.
iex> Exmetrics.Counter.reset "reset_to_zero"
:ok
iex> Exmetrics.Counter.get "reset_to_zero"
0
"""
@spec reset(String.t) :: atom
def reset(name) do
Exmetrics.Worker.reset_counter(name, 0)
end
@doc ~S"""
Reset counter 'name' to n.
iex> Exmetrics.Counter.reset "reset_to_fourty_two", 42
:ok
iex> Exmetrics.Counter.get "reset_to_fourty_two"
42
"""
@spec reset(String.t, integer) :: atom
def reset(name, n) when is_integer(n) do
Exmetrics.Worker.reset_counter(name, n)
end
end
defmodule Histogram do
@moduledoc """
Provides histograms based on
[hdr_histogram](https://github.com/HdrHistogram/hdr_histogram_erl.git).
Use a histogram to track the distribution of a stream of values (e.g., the
latency associated with HTTP requests).
Before loading data from a histogram, create a snapshot via Exmetrics.snapshot/0.
"""
# All automatically associated gauges have to be removed before a histogram is removed.
@doc false
@automatic_histogram_gauges [
"P50", "P75", "P90", "P95", "P99", "P999", # Percentiles
"Max", "Min", "Mean", "Stddev", "Count"
]
def automatic_histogram_gauges, do: @automatic_histogram_gauges
@doc """
Create a new histogram.
iex> Exmetrics.Histogram.new "sample_histogram", 1000000, 3
:ok
iex> Exmetrics.Gauge.get "sample_histogram.P50"
0.0
# Record some values
iex> Enum.each 0..100, &(Exmetrics.Histogram.record "sample_histogram", &1)
:ok
# A snapshot is required before histogram values are up to date
iex> Exmetrics.snapshot
iex> Exmetrics.Gauge.get "sample_histogram.P50"
50.0
"""
@spec new(String.t, integer, integer) :: atom
def new(name, max, sigfigs \\ 3) do
Exmetrics.Worker.new_histogram(name, max, sigfigs)
# Register gauges for various percentiles
#
# The suffixes are listed in the module attribute @automatic_histogram_gauges.
# All automatically associated gauges have to be removed before a histogram is removed.
Exmetrics.Gauge.set("#{name}.P50", fn -> percentile(name, 50.0) end)
Exmetrics.Gauge.set("#{name}.P75", fn -> percentile(name, 75.0) end)
Exmetrics.Gauge.set("#{name}.P90", fn -> percentile(name, 90.0) end)
Exmetrics.Gauge.set("#{name}.P95", fn -> percentile(name, 95.0) end)
Exmetrics.Gauge.set("#{name}.P99", fn -> percentile(name, 99.0) end)
Exmetrics.Gauge.set("#{name}.P999", fn -> percentile(name, 99.9) end)
Exmetrics.Gauge.set("#{name}.Max", fn -> hdr_histogram_apply(name, "max") end)
Exmetrics.Gauge.set("#{name}.Min", fn -> hdr_histogram_apply(name, "min") end)
Exmetrics.Gauge.set("#{name}.Mean", fn -> hdr_histogram_apply(name, "mean") end)
Exmetrics.Gauge.set("#{name}.Stddev", fn -> hdr_histogram_apply(name, "stddev") end)
Exmetrics.Gauge.set("#{name}.Count", fn -> hdr_histogram_apply(name, "get_total_count") end)
end
defp percentile(name, pct) do
# Load the 'merged' histogram view
get(name)
|> :hdr_histogram.percentile(pct)
end
defp hdr_histogram_apply(name, func) when is_bitstring(func) do
# Load the 'merged' histogram view.
# It is built when snapshot() is invoked.
h = get(name)
apply(:hdr_histogram, String.to_atom(func), [h])
end
@doc """
Get a full histogram.
iex> Exmetrics.Histogram.new "h1", 1000, 3
:ok
iex> h = Exmetrics.Histogram.get "h1"
iex> :hdr_histogram.get_total_count(h)
0
"""
@spec get(String.t) :: :hdr_histogram
def get(name) do
Exmetrics.Worker.get_histogram(name)[:merged]
end
@doc """
Record a value in a histogram.
iex> Exmetrics.Histogram.new "h2", 1000, 3
:ok
iex> Exmetrics.Histogram.record "h2", 5
iex> Exmetrics.snapshot
iex> Exmetrics.Histogram.get("h2") |> :hdr_histogram.max
5
"""
@spec record(String.t, integer) :: atom
def record(name, value) do
Exmetrics.Worker.record_histogram_value(name, value)
end
@doc """
Remove histogram and clear resources.
iex> Exmetrics.Histogram.new "h3", 1000, 3
:ok
iex> Exmetrics.Histogram.remove "h3"
:ok
iex> Exmetrics.Histogram.get "h3"
nil
"""
@spec remove(String.t) :: atom
def remove(name) do
Exmetrics.Worker.remove_histogram(name)
end
end
@doc false
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# Define workers and child supervisors to be supervised
worker(Exmetrics.Worker, []),
worker(Exmetrics.Rotator, []),
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Exmetrics.Supervisor]
Supervisor.start_link(children, opts)
end
end