Current section

Files

Jump to
metrics_reader src counter_histogram.erl
Raw

src/counter_histogram.erl

%% @doc A stateful wrapper around a folsom histogram that serves to accumulate
%% scalar metrics over a pre-configured interval. It is essentially a
%% transformation between a scalar value and a histogram.
%% Currently, only counters are supported, but it is possible to generalize to
%% any scalar type and backend using the erlang-metrics library interface here:
%% https://github.com/benoitc/erlang-metrics
%% @end
-module(counter_histogram).
-behaviour(gen_server).
-include("metrics_reader.hrl").
%% API
-export([start_link/0,
start/0,
stop/0,
new/1,
inc/1,
inc/2,
dec/1,
dec/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {running = false :: boolean(),
registry = sets:new(),
slide_interval :: pos_integer(),
acc_interval :: pos_integer(),
timer_ref :: reference()}).
-type state() :: #state{}.
%%%===================================================================
%%% API
%%%===================================================================
-spec start_link() -> gen_server_startlink_ret().
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec start() -> ok.
start() ->
gen_server:call(?SERVER, start).
-spec stop() -> ok.
stop() ->
gen_server:call(?SERVER, stop).
-spec new(term()) -> ok.
new(Name) ->
gen_server:call(?SERVER, {new, Name}).
-spec inc(term()) -> [integer()] | integer().
inc(Name) ->
inc(Name, 1).
-spec inc(term(), pos_integer()) -> [integer()] | integer().
inc(Name, N) ->
Internal = internal_name(Name),
folsom_metrics_counter:inc(Internal, N).
-spec dec(term()) -> [integer()] | integer().
dec(Name) ->
dec(Name, 1).
-spec dec(term(), pos_integer()) -> [integer()] | integer().
dec(Name, N) ->
Internal = internal_name(Name),
folsom_metrics_counter:dec(Internal, N).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
-spec init([]) -> {ok, state()}.
init([]) ->
%% We want a high priority to ensure reporting accuracy
process_flag(priority, high),
SInterval = metrics_reader_helper:opt(histogram_slide_interval_sec, 60),
AInterval = metrics_reader_helper:opt(histogram_acc_interval_sec, 1),
{ok, #state{slide_interval = SInterval,
acc_interval = AInterval * 1000}}.
-spec handle_call(any(), any(), state()) -> {reply, term(), state()}.
handle_call(start, _From,
State = #state{acc_interval = AccInterval, running = false}) ->
TRef = erlang:send_after(AccInterval, self(), tick),
Reply = ok,
{reply, Reply, State#state{running = true, timer_ref = TRef}};
handle_call(stop, _From, State = #state{running = true, timer_ref = TRef}) ->
erlang:cancel_timer(TRef),
Reply = ok,
{reply, Reply, State#state{running = false}};
handle_call({new, Name}, _From,
State = #state{running = true, slide_interval = SlideInterval,
registry = Registry}) ->
folsom_metrics:new_histogram(Name, slide, SlideInterval),
folsom_metrics:new_counter(internal_name(Name)),
Registry1 = sets:add_element(Name, Registry),
{reply, ok, State#state{registry = Registry1}};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
-spec handle_cast(any(), state()) -> {noreply, state()}.
handle_cast(_Msg, State) ->
{noreply, State}.
-spec handle_info(any(), state()) -> {noreply, state()} |
{stop, any(), state()}.
handle_info(tick, State = #state{running = true, registry = Registry,
acc_interval = AccInterval}) ->
lists:foreach(fun (Name) ->
Internal = internal_name(Name),
Value = folsom_metrics_counter:get_value(Internal),
folsom_metrics:notify({Name, Value}),
folsom_metrics_counter:clear(Internal)
end, sets:to_list(Registry)),
TRef = erlang:send_after(AccInterval, self(), tick),
{noreply, State#state{timer_ref = TRef}};
handle_info(_Info, State) ->
{noreply, State}.
-spec terminate(any(), any()) -> ok.
terminate(_Reason, #state{registry = Registry}) ->
lists:foreach(fun (Name) ->
Internal = internal_name(Name),
folsom_metrics:delete_metric(Internal)
end, sets:to_list(Registry)),
ok.
-spec code_change(any(), state(), any()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec internal_name(term()) -> {atom(), term()}.
internal_name(Name) ->
{counter_hist, Name}.