|
|
@@ -19,34 +19,36 @@
|
|
|
-behaviour(gen_server).
|
|
|
|
|
|
%% API functions
|
|
|
--export([ start_link/1
|
|
|
- , stop/1
|
|
|
- , child_spec/1
|
|
|
- ]).
|
|
|
-
|
|
|
--export([ inc/3
|
|
|
- , inc/4
|
|
|
- , get/3
|
|
|
- , get_rate/2
|
|
|
- , get_counters/2
|
|
|
- , create_metrics/3
|
|
|
- , create_metrics/4
|
|
|
- , clear_metrics/2
|
|
|
- , reset_metrics/2
|
|
|
- , has_metrics/2
|
|
|
- ]).
|
|
|
-
|
|
|
--export([ get_metrics/2
|
|
|
- ]).
|
|
|
+-export([
|
|
|
+ start_link/1,
|
|
|
+ stop/1,
|
|
|
+ child_spec/1
|
|
|
+]).
|
|
|
+
|
|
|
+-export([
|
|
|
+ inc/3,
|
|
|
+ inc/4,
|
|
|
+ get/3,
|
|
|
+ get_rate/2,
|
|
|
+ get_counters/2,
|
|
|
+ create_metrics/3,
|
|
|
+ create_metrics/4,
|
|
|
+ clear_metrics/2,
|
|
|
+ reset_metrics/2,
|
|
|
+ has_metrics/2
|
|
|
+]).
|
|
|
+
|
|
|
+-export([get_metrics/2]).
|
|
|
|
|
|
%% gen_server callbacks
|
|
|
--export([ init/1
|
|
|
- , handle_call/3
|
|
|
- , handle_info/2
|
|
|
- , handle_cast/2
|
|
|
- , code_change/3
|
|
|
- , terminate/2
|
|
|
- ]).
|
|
|
+-export([
|
|
|
+ init/1,
|
|
|
+ handle_call/3,
|
|
|
+ handle_info/2,
|
|
|
+ handle_cast/2,
|
|
|
+ code_change/3,
|
|
|
+ terminate/2
|
|
|
+]).
|
|
|
|
|
|
-ifndef(TEST).
|
|
|
-define(SECS_5M, 300).
|
|
|
@@ -75,87 +77,92 @@
|
|
|
-define(SAMPCOUNT_5M, (?SECS_5M div ?SAMPLING)).
|
|
|
|
|
|
-record(rate, {
|
|
|
- max = 0 :: number(),
|
|
|
- current = 0 :: number(),
|
|
|
- last5m = 0 :: number(),
|
|
|
- %% metadata for calculating the avg rate
|
|
|
- tick = 1 :: number(),
|
|
|
- last_v = 0 :: number(),
|
|
|
- %% metadata for calculating the 5min avg rate
|
|
|
- last5m_acc = 0 :: number(),
|
|
|
- last5m_smpl = [] :: list()
|
|
|
- }).
|
|
|
+ max = 0 :: number(),
|
|
|
+ current = 0 :: number(),
|
|
|
+ last5m = 0 :: number(),
|
|
|
+ %% metadata for calculating the avg rate
|
|
|
+ tick = 1 :: number(),
|
|
|
+ last_v = 0 :: number(),
|
|
|
+ %% metadata for calculating the 5min avg rate
|
|
|
+ last5m_acc = 0 :: number(),
|
|
|
+ last5m_smpl = [] :: list()
|
|
|
+}).
|
|
|
|
|
|
-record(state, {
|
|
|
- metric_ids = sets:new(),
|
|
|
- rates :: undefined | #{metric_id() => #rate{}}
|
|
|
- }).
|
|
|
+ metric_ids = sets:new(),
|
|
|
+ rates :: undefined | #{metric_id() => #rate{}}
|
|
|
+}).
|
|
|
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% APIs
|
|
|
%%------------------------------------------------------------------------------
|
|
|
|
|
|
--spec(child_spec(handler_name()) -> supervisor:child_spec()).
|
|
|
+-spec child_spec(handler_name()) -> supervisor:child_spec().
|
|
|
child_spec(Name) ->
|
|
|
- #{ id => emqx_plugin_libs_metrics
|
|
|
- , start => {emqx_plugin_libs_metrics, start_link, [Name]}
|
|
|
- , restart => permanent
|
|
|
- , shutdown => 5000
|
|
|
- , type => worker
|
|
|
- , modules => [emqx_plugin_libs_metrics]
|
|
|
- }.
|
|
|
-
|
|
|
--spec(create_metrics(handler_name(), metric_id(), [atom()]) -> ok | {error, term()}).
|
|
|
+ #{
|
|
|
+ id => emqx_plugin_libs_metrics,
|
|
|
+ start => {emqx_plugin_libs_metrics, start_link, [Name]},
|
|
|
+ restart => permanent,
|
|
|
+ shutdown => 5000,
|
|
|
+ type => worker,
|
|
|
+ modules => [emqx_plugin_libs_metrics]
|
|
|
+ }.
|
|
|
+
|
|
|
+-spec create_metrics(handler_name(), metric_id(), [atom()]) -> ok | {error, term()}.
|
|
|
create_metrics(Name, Id, Metrics) ->
|
|
|
create_metrics(Name, Id, Metrics, Metrics).
|
|
|
|
|
|
--spec(create_metrics(handler_name(), metric_id(), [atom()], [atom()]) -> ok | {error, term()}).
|
|
|
+-spec create_metrics(handler_name(), metric_id(), [atom()], [atom()]) -> ok | {error, term()}.
|
|
|
create_metrics(Name, Id, Metrics, RateMetrics) ->
|
|
|
gen_server:call(Name, {create_metrics, Id, Metrics, RateMetrics}).
|
|
|
|
|
|
--spec(clear_metrics(handler_name(), metric_id()) -> ok).
|
|
|
+-spec clear_metrics(handler_name(), metric_id()) -> ok.
|
|
|
clear_metrics(Name, Id) ->
|
|
|
gen_server:call(Name, {delete_metrics, Id}).
|
|
|
|
|
|
--spec(reset_metrics(handler_name(), metric_id()) -> ok).
|
|
|
+-spec reset_metrics(handler_name(), metric_id()) -> ok.
|
|
|
reset_metrics(Name, Id) ->
|
|
|
gen_server:call(Name, {reset_metrics, Id}).
|
|
|
|
|
|
--spec(has_metrics(handler_name(), metric_id()) -> boolean()).
|
|
|
+-spec has_metrics(handler_name(), metric_id()) -> boolean().
|
|
|
has_metrics(Name, Id) ->
|
|
|
case get_ref(Name, Id) of
|
|
|
not_found -> false;
|
|
|
_ -> true
|
|
|
end.
|
|
|
|
|
|
--spec(get(handler_name(), metric_id(), atom() | integer()) -> number()).
|
|
|
+-spec get(handler_name(), metric_id(), atom() | integer()) -> number().
|
|
|
get(Name, Id, Metric) ->
|
|
|
case get_ref(Name, Id) of
|
|
|
- not_found -> 0;
|
|
|
+ not_found ->
|
|
|
+ 0;
|
|
|
Ref when is_atom(Metric) ->
|
|
|
counters:get(Ref, idx_metric(Name, Id, Metric));
|
|
|
Ref when is_integer(Metric) ->
|
|
|
counters:get(Ref, Metric)
|
|
|
end.
|
|
|
|
|
|
--spec(get_rate(handler_name(), metric_id()) -> map()).
|
|
|
+-spec get_rate(handler_name(), metric_id()) -> map().
|
|
|
get_rate(Name, Id) ->
|
|
|
gen_server:call(Name, {get_rate, Id}).
|
|
|
|
|
|
--spec(get_counters(handler_name(), metric_id()) -> map()).
|
|
|
+-spec get_counters(handler_name(), metric_id()) -> map().
|
|
|
get_counters(Name, Id) ->
|
|
|
- maps:map(fun(_Metric, Index) ->
|
|
|
+ maps:map(
|
|
|
+ fun(_Metric, Index) ->
|
|
|
get(Name, Id, Index)
|
|
|
- end, get_indexes(Name, Id)).
|
|
|
+ end,
|
|
|
+ get_indexes(Name, Id)
|
|
|
+ ).
|
|
|
|
|
|
-spec reset_counters(handler_name(), metric_id()) -> ok.
|
|
|
reset_counters(Name, Id) ->
|
|
|
Indexes = maps:values(get_indexes(Name, Id)),
|
|
|
Ref = get_ref(Name, Id),
|
|
|
- [counters:put(Ref, Idx, 0) || Idx <- Indexes ],
|
|
|
+ [counters:put(Ref, Idx, 0) || Idx <- Indexes],
|
|
|
ok.
|
|
|
|
|
|
--spec(get_metrics(handler_name(), metric_id()) -> metrics()).
|
|
|
+-spec get_metrics(handler_name(), metric_id()) -> metrics().
|
|
|
get_metrics(Name, Id) ->
|
|
|
#{rate => get_rate(Name, Id), counters => get_counters(Name, Id)}.
|
|
|
|
|
|
@@ -180,47 +187,63 @@ init(Name) ->
|
|
|
handle_call({get_rate, _Id}, _From, State = #state{rates = undefined}) ->
|
|
|
{reply, make_rate(0, 0, 0), State};
|
|
|
handle_call({get_rate, Id}, _From, State = #state{rates = Rates}) ->
|
|
|
- {reply, case maps:get(Id, Rates, undefined) of
|
|
|
- undefined -> make_rate(0, 0, 0);
|
|
|
- RatesPerId -> format_rates_of_id(RatesPerId)
|
|
|
- end, State};
|
|
|
-
|
|
|
-handle_call({create_metrics, Id, Metrics, RateMetrics}, _From,
|
|
|
- State = #state{metric_ids = MIDs, rates = Rates}) ->
|
|
|
+ {reply,
|
|
|
+ case maps:get(Id, Rates, undefined) of
|
|
|
+ undefined -> make_rate(0, 0, 0);
|
|
|
+ RatesPerId -> format_rates_of_id(RatesPerId)
|
|
|
+ end, State};
|
|
|
+handle_call(
|
|
|
+ {create_metrics, Id, Metrics, RateMetrics},
|
|
|
+ _From,
|
|
|
+ State = #state{metric_ids = MIDs, rates = Rates}
|
|
|
+) ->
|
|
|
case RateMetrics -- Metrics of
|
|
|
[] ->
|
|
|
RatePerId = maps:from_list([{M, #rate{}} || M <- RateMetrics]),
|
|
|
- Rate1 = case Rates of
|
|
|
- undefined -> #{Id => RatePerId};
|
|
|
- _ -> Rates#{Id => RatePerId}
|
|
|
- end,
|
|
|
- {reply, create_counters(get_self_name(), Id, Metrics),
|
|
|
- State#state{metric_ids = sets:add_element(Id, MIDs),
|
|
|
- rates = Rate1}};
|
|
|
+ Rate1 =
|
|
|
+ case Rates of
|
|
|
+ undefined -> #{Id => RatePerId};
|
|
|
+ _ -> Rates#{Id => RatePerId}
|
|
|
+ end,
|
|
|
+ {reply, create_counters(get_self_name(), Id, Metrics), State#state{
|
|
|
+ metric_ids = sets:add_element(Id, MIDs),
|
|
|
+ rates = Rate1
|
|
|
+ }};
|
|
|
_ ->
|
|
|
{reply, {error, not_super_set_of, {RateMetrics, Metrics}}, State}
|
|
|
end;
|
|
|
-
|
|
|
-handle_call({delete_metrics, Id}, _From,
|
|
|
- State = #state{metric_ids = MIDs, rates = Rates}) ->
|
|
|
- {reply, delete_counters(get_self_name(), Id),
|
|
|
- State#state{metric_ids = sets:del_element(Id, MIDs),
|
|
|
- rates = case Rates of
|
|
|
- undefined -> undefined;
|
|
|
- _ -> maps:remove(Id, Rates)
|
|
|
- end}};
|
|
|
-
|
|
|
-handle_call({reset_metrics, Id}, _From,
|
|
|
- State = #state{rates = Rates}) ->
|
|
|
- {reply, reset_counters(get_self_name(), Id),
|
|
|
- State#state{rates = case Rates of
|
|
|
- undefined -> undefined;
|
|
|
- _ -> ResetRate =
|
|
|
- maps:map(fun(_Key, _Value) -> #rate{} end,
|
|
|
- maps:get(Id, Rates, #{})),
|
|
|
- maps:put(Id, ResetRate, Rates)
|
|
|
- end}};
|
|
|
-
|
|
|
+handle_call(
|
|
|
+ {delete_metrics, Id},
|
|
|
+ _From,
|
|
|
+ State = #state{metric_ids = MIDs, rates = Rates}
|
|
|
+) ->
|
|
|
+ {reply, delete_counters(get_self_name(), Id), State#state{
|
|
|
+ metric_ids = sets:del_element(Id, MIDs),
|
|
|
+ rates =
|
|
|
+ case Rates of
|
|
|
+ undefined -> undefined;
|
|
|
+ _ -> maps:remove(Id, Rates)
|
|
|
+ end
|
|
|
+ }};
|
|
|
+handle_call(
|
|
|
+ {reset_metrics, Id},
|
|
|
+ _From,
|
|
|
+ State = #state{rates = Rates}
|
|
|
+) ->
|
|
|
+ {reply, reset_counters(get_self_name(), Id), State#state{
|
|
|
+ rates =
|
|
|
+ case Rates of
|
|
|
+ undefined ->
|
|
|
+ undefined;
|
|
|
+ _ ->
|
|
|
+ ResetRate =
|
|
|
+ maps:map(
|
|
|
+ fun(_Key, _Value) -> #rate{} end,
|
|
|
+ maps:get(Id, Rates, #{})
|
|
|
+ ),
|
|
|
+ maps:put(Id, ResetRate, Rates)
|
|
|
+ end
|
|
|
+ }};
|
|
|
handle_call(_Request, _From, State) ->
|
|
|
{reply, ok, State}.
|
|
|
|
|
|
@@ -230,17 +253,21 @@ handle_cast(_Msg, State) ->
|
|
|
handle_info(ticking, State = #state{rates = undefined}) ->
|
|
|
erlang:send_after(timer:seconds(?SAMPLING), self(), ticking),
|
|
|
{noreply, State};
|
|
|
-
|
|
|
handle_info(ticking, State = #state{rates = Rates0}) ->
|
|
|
Rates =
|
|
|
- maps:map(fun(Id, RatesPerID) ->
|
|
|
- maps:map(fun(Metric, Rate) ->
|
|
|
- calculate_rate(get(get_self_name(), Id, Metric), Rate)
|
|
|
- end, RatesPerID)
|
|
|
- end, Rates0),
|
|
|
+ maps:map(
|
|
|
+ fun(Id, RatesPerID) ->
|
|
|
+ maps:map(
|
|
|
+ fun(Metric, Rate) ->
|
|
|
+ calculate_rate(get(get_self_name(), Id, Metric), Rate)
|
|
|
+ end,
|
|
|
+ RatesPerID
|
|
|
+ )
|
|
|
+ end,
|
|
|
+ Rates0
|
|
|
+ ),
|
|
|
erlang:send_after(timer:seconds(?SAMPLING), self(), ticking),
|
|
|
{noreply, State#state{rates = Rates}};
|
|
|
-
|
|
|
handle_info(_Info, State) ->
|
|
|
{noreply, State}.
|
|
|
|
|
|
@@ -269,12 +296,17 @@ create_counters(Name, Id, Metrics) ->
|
|
|
Indexes = maps:from_list(lists:zip(Metrics, lists:seq(1, Size))),
|
|
|
Counters = get_pterm(Name),
|
|
|
CntrRef = counters:new(Size, [write_concurrency]),
|
|
|
- persistent_term:put(?CntrRef(Name),
|
|
|
- Counters#{Id => #{ref => CntrRef, indexes => Indexes}}),
|
|
|
+ persistent_term:put(
|
|
|
+ ?CntrRef(Name),
|
|
|
+ Counters#{Id => #{ref => CntrRef, indexes => Indexes}}
|
|
|
+ ),
|
|
|
%% restore the old counters
|
|
|
- lists:foreach(fun({Metric, N}) ->
|
|
|
+ lists:foreach(
|
|
|
+ fun({Metric, N}) ->
|
|
|
inc(Name, Id, Metric, N)
|
|
|
- end, maps:to_list(OlderCounters)).
|
|
|
+ end,
|
|
|
+ maps:to_list(OlderCounters)
|
|
|
+ ).
|
|
|
|
|
|
delete_counters(Name, Id) ->
|
|
|
persistent_term:put(?CntrRef(Name), maps:remove(Id, get_pterm(Name))).
|
|
|
@@ -299,9 +331,13 @@ get_pterm(Name) ->
|
|
|
|
|
|
calculate_rate(_CurrVal, undefined) ->
|
|
|
undefined;
|
|
|
-calculate_rate(CurrVal, #rate{max = MaxRate0, last_v = LastVal,
|
|
|
- tick = Tick, last5m_acc = AccRate5Min0,
|
|
|
- last5m_smpl = Last5MinSamples0}) ->
|
|
|
+calculate_rate(CurrVal, #rate{
|
|
|
+ max = MaxRate0,
|
|
|
+ last_v = LastVal,
|
|
|
+ tick = Tick,
|
|
|
+ last5m_acc = AccRate5Min0,
|
|
|
+ last5m_smpl = Last5MinSamples0
|
|
|
+}) ->
|
|
|
%% calculate the current rate based on the last value of the counter
|
|
|
CurrRate = (CurrVal - LastVal) / ?SAMPLING,
|
|
|
|
|
|
@@ -317,32 +353,40 @@ calculate_rate(CurrVal, #rate{max = MaxRate0, last_v = LastVal,
|
|
|
case Tick =< ?SAMPCOUNT_5M of
|
|
|
true ->
|
|
|
Acc = AccRate5Min0 + CurrRate,
|
|
|
- {lists:reverse([CurrRate | lists:reverse(Last5MinSamples0)]),
|
|
|
- Acc, Acc / Tick};
|
|
|
+ {lists:reverse([CurrRate | lists:reverse(Last5MinSamples0)]), Acc, Acc / Tick};
|
|
|
false ->
|
|
|
[FirstRate | Rates] = Last5MinSamples0,
|
|
|
- Acc = AccRate5Min0 + CurrRate - FirstRate,
|
|
|
- {lists:reverse([CurrRate | lists:reverse(Rates)]),
|
|
|
- Acc, Acc / ?SAMPCOUNT_5M}
|
|
|
+ Acc = AccRate5Min0 + CurrRate - FirstRate,
|
|
|
+ {lists:reverse([CurrRate | lists:reverse(Rates)]), Acc, Acc / ?SAMPCOUNT_5M}
|
|
|
end,
|
|
|
|
|
|
- #rate{max = MaxRate, current = CurrRate, last5m = Last5Min,
|
|
|
- last_v = CurrVal, last5m_acc = Acc5Min,
|
|
|
- last5m_smpl = Last5MinSamples, tick = Tick + 1}.
|
|
|
+ #rate{
|
|
|
+ max = MaxRate,
|
|
|
+ current = CurrRate,
|
|
|
+ last5m = Last5Min,
|
|
|
+ last_v = CurrVal,
|
|
|
+ last5m_acc = Acc5Min,
|
|
|
+ last5m_smpl = Last5MinSamples,
|
|
|
+ tick = Tick + 1
|
|
|
+ }.
|
|
|
|
|
|
format_rates_of_id(RatesPerId) ->
|
|
|
- maps:map(fun(_Metric, Rates) ->
|
|
|
+ maps:map(
|
|
|
+ fun(_Metric, Rates) ->
|
|
|
format_rate(Rates)
|
|
|
- end, RatesPerId).
|
|
|
+ end,
|
|
|
+ RatesPerId
|
|
|
+ ).
|
|
|
|
|
|
format_rate(#rate{max = Max, current = Current, last5m = Last5Min}) ->
|
|
|
make_rate(Current, Max, Last5Min).
|
|
|
|
|
|
make_rate(Current, Max, Last5Min) ->
|
|
|
- #{ current => precision(Current, 2)
|
|
|
- , max => precision(Max, 2)
|
|
|
- , last5m => precision(Last5Min, 2)
|
|
|
- }.
|
|
|
+ #{
|
|
|
+ current => precision(Current, 2),
|
|
|
+ max => precision(Max, 2),
|
|
|
+ last5m => precision(Last5Min, 2)
|
|
|
+ }.
|
|
|
|
|
|
precision(Float, N) ->
|
|
|
Base = math:pow(10, N),
|