Current section

Files

Jump to
metrics src metrics.erl
Raw

src/metrics.erl

%% Copyright (c) 2016-2018, Benoit Chesneau.
%%
%% This file is part of erlang-metrics released under the BSD license.
%% See the NOTICE for more information.
-module(metrics).
-author("Benoit Chesneau").
-behaviour(gen_server).
-behaviour(application).
%% API
-export([new/2]).
-export([update/1, update/2]).
-export([update_or_create/3]).
-export([backend/0, backend/1]).
-export([delete/1]).
-export([start_link/0]).
%% application callbacks
-export([start/2, stop/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,handle_info/2,terminate/2, code_change/3]).
-include_lib("syntax_tools/include/merl.hrl").
-define(SERVER, ?MODULE).
-define(TAB, metrics).
-record(state, {mod}).
%%%===================================================================
%%% Types
%%%===================================================================
-type metric_name() :: list().
-type metric() :: counter | histogram | gauge | meter | spiral | duration.
-type value() :: any().
-type probe() :: {c, integer()} | timer_start | timer_end | {duration_fun, fun()} | value().
%%%===================================================================
%%% API
%%%===================================================================
%% @doc initialise a metric
-spec new(Metric, Name) -> Result when
Metric :: metric(),
Name :: metric_name(),
Result :: ok.
new(Type, Name) ->
metrics_mod:new(Type, Name).
%% @doc increment a counter with 1
-spec update(Name) -> Result when
Name :: metric_name(),
Result :: ok | any().
update(Name) ->
update(Name, {c, 1}).
%% @doc update a metric
-spec update(Name, Probe) -> Result when
Name :: metric_name(),
Probe :: probe(),
Result :: ok | any().
update(Name, Probe) ->
metrics_mod:update(Name, Probe).
%% @doc update a metric and create it if it doesn't exists
-spec update_or_create(Name, Probe, Metric) -> Result when
Name :: metric_name(),
Probe :: probe(),
Metric :: metric(),
Result :: ok | any().
update_or_create(Name, Probe, Type) ->
metrics_mod:update_or_create(Name, Probe, Type).
%% @doc delete a metric
-spec delete(Name) -> Result when
Name :: metric_name(),
Result :: ok | any().
delete(Name) ->
metrics_mod:delete(Name).
%% @doc retrieve the current backend name
-spec backend() -> atom().
backend() -> gen_server:call(?MODULE, get_backend).
%% @doc set the backend to use
-spec backend(atom()) -> ok.
backend(Mod) ->
_ = code:ensure_loaded(Mod),
case erlang:function_exported(Mod, update, 3) of
true -> ok;
false -> erlang:error(bad_modmetric)
end,
gen_server:call(?MODULE, {set_backend, Mod}).
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% application callbacks
%%%===================================================================
%%%
-spec start(application:start_type(), any()) -> {ok, pid()}.
start(_StartType, _StartArgs) ->
'metrics_sup':start_link().
-spec stop(atom()) -> ok.
stop(_State) ->
ok.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%%
init([]) ->
Mod = metrics_mod(),
Config = init_mod(Mod),
ok = build_metrics_mod(Mod, Config),
{ok, #state{mod=Mod}}.
handle_call(get_backend, _From, State) ->
{reply, State#state.mod, State};
handle_call({set_backend, Mod}, _From, State) ->
if
State#state.mod /= Mod ->
Config = init_mod(Mod),
build_metrics_mod(Mod, Config);
true -> ok
end,
{reply, ok, State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
metrics_mod() ->
Mod = application:get_env(metrics, metrics_mod, metrics_noop),
_ = code:ensure_loaded(Mod),
case erlang:function_exported(Mod, update, 3) of
false ->
error_logger:error_msg("bad metric module"),
erlang:error(bad_modmetric);
true ->
Mod
end.
init_mod(Mod) ->
case erlang:function_exported(Mod, init, 0) of
false -> #{};
true -> Mod:init()
end.
build_metrics_mod(Mod, Config) when is_atom(Mod), is_map(Config) ->
error_logger:info_msg("build metrics module: ~s~n", [Mod]),
Module = ?Q("-module('metrics_mod')."),
Exported = ?Q("-export(['new'/2, 'update'/2, 'update_or_create'/3, 'delete'/1])."),
Functions = ?Q([
"new(Type, Name) -> _@Mod@:new(Type, Name, _@Config@).",
"update(Name, Probe) -> _@Mod@:update(Name, Probe, _@Config@).",
"update_or_create(Name, Probe, Type) ->",
" _@Mod@:update_or_create(Name, Probe, Type, _@Config@).",
"delete(Name) -> _@Mod@:delete(Name, _@Config@)."
]),
Forms = lists:flatten([Module, Exported, Functions]),
merl:compile_and_load(Forms, [verbose]),
ok.