Packages

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

Current section

Files

Jump to
exmetrics lib worker.ex
Raw

lib/worker.ex

defmodule Exmetrics.Worker do
@moduledoc false
use GenServer
require Logger
def start_link() do
GenServer.start_link(__MODULE__, %{counters: %{}, gauges: %{}, histograms: %{}}, name: Exmetrics)
end
###
# Snapshot
###
@doc "Snapshot current state."
def state() do
GenServer.call Exmetrics, :get_state
end
###
# Gauges
###
@doc "Set gauge to value n."
def set_gauge(name, n) do
GenServer.cast Exmetrics, {:set, [:gauges, name], n}
end
@doc "Get gauge value."
def get_gauge(name) do
v = GenServer.call Exmetrics, {:get, [:gauges, name]}
case v do
v when is_function(v) -> apply(v, [])
nil -> nil
end
end
@doc "Remove gauge from collection."
def remove_gauge(name) do
remove([:gauges, name])
end
defp remove(path) do
GenServer.cast Exmetrics, {:remove, path}
end
###
# Counters
###
@doc "Increment counter by n."
def increment_counter(name, n) do
GenServer.cast Exmetrics, {:increment_counter, name, n}
end
@doc "Returns the counter value with 'name'. Returns 0 if this counter is unknown."
def get_counter(name) do
GenServer.call Exmetrics, {:get, [:counters, name]}
end
@doc "Reset counter."
def reset_counter(name, n \\ 0) do
GenServer.cast Exmetrics, {:set, [:counters, name], n}
end
###
# Histograms
###
@doc """
Create a sliding window histogram which records metrics over 5 minutes.
"""
def new_histogram(name, max, sigfigs) when is_integer(max) and is_integer(sigfigs) do
init_histogram = fn _ ->
{:ok, h} = :hdr_histogram.open(max, sigfigs)
h
end
# initialize window, allocate one for a combined view ('merged')
histograms = 0..5
|> Enum.map(init_histogram)
# take one for merges
[merged|histograms] = histograms
# pointer to the current histogram
current = hd(histograms)
window = %{
index: 0,
current: current,
merged: merged,
histograms: histograms
}
GenServer.cast Exmetrics, {:set, [:histograms, name], window}
end
@doc "Register a value inside a histogram."
def record_histogram_value(name, value) when is_integer(value) do
GenServer.cast Exmetrics, {:record_h, [:histograms, name, :current], value}
end
@doc "Get a full histogram."
def get_histogram(name) do
GenServer.call Exmetrics, {:get, [:histograms, name]}
end
@doc """
Rotate the current recording window of a histogram.
Before rotation (*current window):
[*0 | 1 | 2 | 3 | 4]
After rotation:
[0 | *1 | 2 | 3 | 4]
The window is cleared *after* it was rotated to.
"""
def rotate_histogram(histogram) when is_bitstring(histogram) do
case GenServer.call Exmetrics, {:get, [:histograms, histogram]} do
nil ->
:err_not_avail
h -> rotate_histogram(h)
end
end
def rotate_histogram({key, %{index: index, histograms: histograms} = histogram}) do
index = index+1
current = Enum.at(histograms, rem(index, length(histograms)))
:hdr_histogram.reset(current)
histogram = %{histogram | current: current, index: index}
GenServer.cast Exmetrics, {:set, [:histograms, key], histogram}
end
@doc "Rotate all registered histograms."
def rotate_all_histograms() do
get_in(state(), [:histograms])
|> Enum.each(&rotate_histogram/1)
end
def remove_histogram(histogram_name) do
case GenServer.call Exmetrics, {:get, [:histograms, histogram_name]} do
nil ->
:err_not_avail
hs ->
# Remove all automatically registered gauges.
Exmetrics.Histogram.automatic_histogram_gauges
|> Enum.each(fn gauge_name -> remove_gauge "#{histogram_name}.#{gauge_name}" end)
# Close all histogram windows
hs
|> Map.get(:histograms)
|> Enum.each(&:hdr_histogram.close/1)
# ...and the histogram which contains the merged view
Map.get(hs, :merged)
|> :hdr_histogram.close
remove([:histograms, histogram_name])
:ok
end
end
###
# Server Functions
###
def handle_call({:get, path}, _from, state) do
{:reply, get_in(state, path), state}
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
def handle_cast({:increment_counter, counter_name, n}, state) do
state = update_in(state, [:counters, counter_name], &(incr(&1, n)))
{:noreply, state}
end
def handle_cast({:set, path, value}, state) do
state = update_in(state, path, fn _ -> value end)
{:noreply, state}
end
def handle_cast({:remove, path}, state) do
state = update_in(state, path, fn _ -> nil end)
{:noreply, state}
end
def handle_cast({:record_h, path, value}, state) do
histogram = get_in(state, path)
case histogram do
nil -> nil
histogram -> :hdr_histogram.record(histogram, value)
end
{:noreply, state}
end
###
# Utility functions
###
defp incr(a, b) when is_nil(a) do
b
end
defp incr(a, b) do
a + b
end
end