meter reader initial merge
diff --git a/include/folsom.hrl b/include/folsom.hrl
index f1995f8..6715a67 100644
--- a/include/folsom.hrl
+++ b/include/folsom.hrl
@@ -3,6 +3,7 @@
-define(GAUGE_TABLE, folsom_gauges).
-define(HISTOGRAM_TABLE, folsom_histograms).
-define(METER_TABLE, folsom_meters).
+-define(METER_READER_TABLE, folsom_meter_readers).
-define(HISTORY_TABLE, folsom_histories).
-define(DEFAULT_LIMIT, 5).
diff --git a/src/folsom_ets.erl b/src/folsom_ets.erl
index c42d93f..7c2e34f 100644
--- a/src/folsom_ets.erl
+++ b/src/folsom_ets.erl
@@ -136,6 +136,8 @@
folsom_metrics_history:get_events(Name);
get_values(Name, meter) ->
folsom_metrics_meter:get_values(Name);
+get_values(Name, meter_reader) ->
+ folsom_metrics_meter_reader:get_values(Name);
get_values(_, Type) ->
{error, Type, unsupported_metric_type}.
@@ -163,10 +165,15 @@
true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = history, history_size = ?DEFAULT_SIZE}}),
ok;
maybe_add_handler(meter, Name, false) ->
- ok = folsom_meter_timer_server:register(Name),
+ ok = folsom_meter_timer_server:register(Name, folsom_metrics_meter),
true = folsom_metrics_meter:new(Name),
true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = meter}}),
ok;
+maybe_add_handler(meter_reader, Name, false) ->
+ ok = folsom_meter_timer_server:register(Name, folsom_metrics_meter_reader),
+ true = folsom_metrics_meter_reader:new(Name),
+ true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = meter_reader}}),
+ ok;
maybe_add_handler(Type, _, false) ->
{error, Type, unsupported_metric_type};
maybe_add_handler(_, Name, true) ->
@@ -226,6 +233,10 @@
delete_metric(Name, meter) ->
true = ets:delete(?METER_TABLE, Name),
true = ets:delete(?FOLSOM_TABLE, Name),
+ ok;
+delete_metric(Name, meter_reader) ->
+ true = ets:delete(?METER_READER_TABLE, Name),
+ true = ets:delete(?FOLSOM_TABLE, Name),
ok.
delete_histogram(Name, #histogram{type = uniform, sample = #uniform{reservoir = Reservoir}}) ->
@@ -287,6 +298,13 @@
add_handler(meter, Name),
folsom_metrics_meter:mark(Name, Value),
ok;
+notify(Name, Value, meter_reader, true) ->
+ folsom_metrics_meter_reader:mark(Name, Value),
+ ok;
+notify(Name, Value, meter_reader, false) ->
+ add_handler(meter, Name),
+ folsom_metrics_meter_reader:mark(Name, Value),
+ ok;
notify(_, _, Type, _) ->
{error, Type, unsupported_metric_type}.
diff --git a/src/folsom_meter_timer_server.erl b/src/folsom_meter_timer_server.erl
index 4538b38..5a38c07 100644
--- a/src/folsom_meter_timer_server.erl
+++ b/src/folsom_meter_timer_server.erl
@@ -28,7 +28,7 @@
-behaviour(gen_server).
%% API
--export([start_link/0, register/1, dump/0]).
+-export([start_link/0, register/2, dump/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -88,12 +88,12 @@
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-handle_call({register, Name}, _From, State) ->
+handle_call({register, Name, Module}, _From, State) ->
NewState = case proplists:is_defined(Name, State#state.registered_timers) of
true ->
State;
false ->
- {ok, Ref} = timer:apply_interval(?DEFAULT_INTERVAL, folsom_metrics_meter, tick, [Name]),
+ {ok, Ref} = timer:apply_interval(?DEFAULT_INTERVAL, Module, tick, [Name]),
NewList = [{Name, Ref} | State#state.registered_timers],
#state{registered_timers = NewList}
end,
@@ -156,8 +156,9 @@
%%% Internal functions
%%%===================================================================
-register(Name) ->
- gen_server:call(?SERVER, {register, Name}).
+%% Name of the metric and name of the module used to tick said metric
+register(Name, Module) ->
+ gen_server:call(?SERVER, {register, Name, Module}).
dump() ->
gen_server:call(?SERVER, dump).
diff --git a/src/folsom_metrics.erl b/src/folsom_metrics.erl
index 4a7f238..9e30eae 100644
--- a/src/folsom_metrics.erl
+++ b/src/folsom_metrics.erl
@@ -34,6 +34,7 @@
new_history/1,
new_history/2,
new_meter/1,
+ new_meter_reader/1,
delete_metric/1,
notify/1,
notify/2,
@@ -83,6 +84,9 @@
new_meter(Name) ->
folsom_ets:add_handler(meter, Name).
+new_meter_reader(Name) ->
+ folsom_ets:add_handler(meter_reader, Name).
+
delete_metric(Name) ->
folsom_ets:delete_handler(Name).
diff --git a/src/folsom_metrics_meter.erl b/src/folsom_metrics_meter.erl
index 5aa9ba0..5f8236b 100644
--- a/src/folsom_metrics_meter.erl
+++ b/src/folsom_metrics_meter.erl
@@ -47,27 +47,51 @@
OneMin = folsom_ewma:one_minute_ewma(),
FiveMin = folsom_ewma:five_minute_ewma(),
FifteenMin = folsom_ewma:fifteen_minute_ewma(),
- ets:insert(?METER_TABLE,{Name, #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin, start_time = folsom_utils:now_epoch_micro()}}).
+
+ ets:insert(?METER_TABLE,
+ {Name, #meter{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin,
+ start_time = folsom_utils:now_epoch_micro()}}).
tick(Name) ->
- #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin} = Meter = get_value(Name),
+ #meter{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = Meter = get_value(Name),
+
OneMin1 = folsom_ewma:tick(OneMin),
FiveMin1 = folsom_ewma:tick(FiveMin),
FifteenMin1 = folsom_ewma:tick(FifteenMin),
- ets:insert(?METER_TABLE, {Name, Meter#meter{one = OneMin1, five = FiveMin1, fifteen = FifteenMin1}}).
+
+ ets:insert(?METER_TABLE,
+ {Name, Meter#meter{one = OneMin1,
+ five = FiveMin1,
+ fifteen = FifteenMin1}}).
mark(Name) ->
mark(Name, 1).
mark(Name, Value) ->
- #meter{count = Count, one = OneMin, five = FiveMin, fifteen = FifteenMin} = Meter = get_value(Name),
+ #meter{count = Count,
+ one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = Meter = get_value(Name),
+
OneMin1 = folsom_ewma:update(OneMin, Value),
FiveMin1 = folsom_ewma:update(FiveMin, Value),
FifteenMin1 = folsom_ewma:update(FifteenMin, Value),
- ets:insert(?METER_TABLE, {Name, Meter#meter{count = Count + Value, one = OneMin1, five = FiveMin1, fifteen = FifteenMin1}}).
+
+ ets:insert(?METER_TABLE, {Name, Meter#meter{count = Count + Value,
+ one = OneMin1,
+ five = FiveMin1,
+ fifteen = FifteenMin1}}).
get_values(Name) ->
- #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin, count = Count} = Meter = get_value(Name),
+ #meter{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin,
+ count = Count} = Meter = get_value(Name),
+
L = [
{count, Count},
{one, get_rate(OneMin)},
@@ -76,10 +100,14 @@
{mean, get_mean_rate(Meter)},
{acceleration, get_acceleration(Name)}
],
+
[ {K,V} || {K,V} <- L, V /= undefined ].
get_acceleration(Name) ->
- #meter{one = OneMin, five = FiveMin, fifteen = FifteenMin} = get_value(Name),
+ #meter{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = get_value(Name),
+
[
{one_to_five, calc_acceleration(get_rate(OneMin), get_rate(FiveMin), 300)},
{five_to_fifteen, calc_acceleration(get_rate(FiveMin), get_rate(FifteenMin), 600)},
@@ -105,7 +133,8 @@
Count / Elapsed.
calc_acceleration(Rate1, Rate2, Interval) ->
- get_rate(Rate1, Rate2, Interval). % most current velocity minus previous velocity
+ % most current velocity minus previous velocity
+ get_rate(Rate1, Rate2, Interval).
get_rate(Value1, Value2, Interval) ->
Delta = Value1 - Value2,
diff --git a/src/folsom_metrics_meter_reader.erl b/src/folsom_metrics_meter_reader.erl
new file mode 100644
index 0000000..50b15b9
--- /dev/null
+++ b/src/folsom_metrics_meter_reader.erl
@@ -0,0 +1,152 @@
+%%%
+%%% Copyright 2011, Boundary
+%%% Copyright 2011, Opscode
+%%%
+%%% Licensed under the Apache License, Version 2.0 (the "License");
+%%% you may not use this file except in compliance with the License.
+%%% You may obtain a copy of the License at
+%%%
+%%% http://www.apache.org/licenses/LICENSE-2.0
+%%%
+%%% Unless required by applicable law or agreed to in writing, software
+%%% distributed under the License is distributed on an "AS IS" BASIS,
+%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%%% See the License for the specific language governing permissions and
+%%% limitations under the License.
+%%%
+
+
+%%%-------------------------------------------------------------------
+%%% File: folsom_metrics_meter_reader.erl
+%%% @author Seth Falcon <seth@opscode.com>
+%%% @author joe williams <j@boundary.com>
+%%% @doc
+%%% @end
+%%%------------------------------------------------------------------
+
+-module(folsom_metrics_meter_reader).
+
+-export([new/1,
+ tick/1,
+ mark/1,
+ mark/2,
+ get_values/1,
+ get_acceleration/1
+ ]).
+
+
+-record(meter_reader, {
+ one,
+ five,
+ fifteen,
+ count = 0,
+ start_time,
+ last_count = unset
+ }).
+
+-include("folsom.hrl").
+
+new(Name) ->
+ OneMin = folsom_ewma:one_minute_ewma(),
+ FiveMin = folsom_ewma:five_minute_ewma(),
+ FifteenMin = folsom_ewma:fifteen_minute_ewma(),
+
+ ets:insert(?METER_READER_TABLE,
+ {Name, #meter_reader{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin,
+ start_time = folsom_utils:now_epoch_micro()}}).
+
+tick(Name) ->
+ #meter_reader{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = Meter = get_value(Name),
+
+ OneMin1 = folsom_ewma:tick(OneMin),
+ FiveMin1 = folsom_ewma:tick(FiveMin),
+ FifteenMin1 = folsom_ewma:tick(FifteenMin),
+
+ ets:insert(?METER_READER_TABLE,
+ {Name, Meter#meter_reader{one = OneMin1,
+ five = FiveMin1,
+ fifteen = FifteenMin1}}).
+
+mark(Name) ->
+ mark(Name, 1).
+
+mark(Name, Value) ->
+ % skip first reading to bootstrap last value
+ #meter_reader{count = Count,
+ last_count = LastCount,
+ one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = Meter = get_value(Name),
+
+ NewMeter = case LastCount of
+ unset ->
+ Meter#meter_reader{last_count = Value};
+ _ ->
+ Delta = Value - LastCount,
+ OneMin1 = folsom_ewma:update(OneMin, Delta),
+ FiveMin1 = folsom_ewma:update(FiveMin, Delta),
+ FifteenMin1 = folsom_ewma:update(FifteenMin, Delta),
+ Meter#meter_reader{count = Count + Delta,
+ last_count = Value,
+ one = OneMin1,
+ five = FiveMin1,
+ fifteen = FifteenMin1}
+ end,
+
+ ets:insert(?METER_READER_TABLE, {Name, NewMeter}).
+
+get_values(Name) ->
+ #meter_reader{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = Meter = get_value(Name),
+
+ L = [
+ {one, get_rate(OneMin)},
+ {five, get_rate(FiveMin)},
+ {fifteen, get_rate(FifteenMin)},
+ {mean, get_mean_rate(Meter)},
+ {acceleration, get_acceleration(Name)}
+ ],
+
+ [ {K,V} || {K,V} <- L, V /= undefined ].
+
+get_acceleration(Name) ->
+ #meter_reader{one = OneMin,
+ five = FiveMin,
+ fifteen = FifteenMin} = get_value(Name),
+
+ [
+ {one_to_five, calc_acceleration(get_rate(OneMin), get_rate(FiveMin), 300)},
+ {five_to_fifteen, calc_acceleration(get_rate(FiveMin), get_rate(FifteenMin), 600)},
+ {one_to_fifteen, calc_acceleration(get_rate(OneMin), get_rate(FifteenMin), 900)}
+ ].
+
+% internal functions
+
+get_rate(EWMA) ->
+ folsom_ewma:rate(EWMA).
+
+get_mean_rate(#meter_reader{count = Count, start_time = Start}) ->
+ calc_mean_rate(Start, Count).
+
+get_value(Name) ->
+ [{_, Value}] = ets:lookup(?METER_READER_TABLE, Name),
+ Value.
+
+calc_mean_rate(_, 0) ->
+ 0.0;
+calc_mean_rate(Start, Count) ->
+ Elapsed = folsom_utils:now_epoch_micro() - Start,
+ Count / Elapsed.
+
+calc_acceleration(Rate1, Rate2, Interval) ->
+ % most current velocity minus previous velocity
+ get_rate(Rate1, Rate2, Interval).
+
+get_rate(Value1, Value2, Interval) ->
+ Delta = Value1 - Value2,
+ Delta / Interval.
diff --git a/src/folsom_sup.erl b/src/folsom_sup.erl
index a7c0ba2..cd39d40 100644
--- a/src/folsom_sup.erl
+++ b/src/folsom_sup.erl
@@ -102,6 +102,7 @@
{?GAUGE_TABLE, [set, named_table, public, {write_concurrency, true}]},
{?HISTOGRAM_TABLE, [set, named_table, public, {write_concurrency, true}]},
{?METER_TABLE, [set, named_table, public, {write_concurrency, true}]},
+ {?METER_READER_TABLE, [set, named_table, public, {write_concurrency, true}]},
{?HISTORY_TABLE, [set, named_table, public, {write_concurrency, true}]}
],
[maybe_create_table(ets:info(Name), Name, Opts) || {Name, Opts} <- Tables],
diff --git a/test/folsom_erlang_checks.erl b/test/folsom_erlang_checks.erl
index 55bf25b..352cdb3 100644
--- a/test/folsom_erlang_checks.erl
+++ b/test/folsom_erlang_checks.erl
@@ -57,19 +57,21 @@
ok = folsom_metrics:new_history(<<"history">>),
ok = folsom_metrics:new_meter(meter),
+ ok = folsom_metrics:new_meter_reader(meter_reader),
+
?debugFmt("ensuring meter tick is registered with gen_server~n", []),
- ok = ensure_meter_tick_exists(meter),
+ ok = ensure_meter_tick_exists(),
?debugFmt("ensuring multiple timer registrations dont cause issues", []),
- ok = folsom_meter_timer_server:register(meter),
- ok = folsom_meter_timer_server:register(meter),
- ok = folsom_meter_timer_server:register(meter),
+ ok = folsom_meter_timer_server:register(meter, folsom_metrics_meter),
+ ok = folsom_meter_timer_server:register(meter, folsom_metrics_meter),
+ ok = folsom_meter_timer_server:register(meter, folsom_metrics_meter),
?debugFmt("~p", [folsom_meter_timer_server:dump()]),
{state, List} = folsom_meter_timer_server:dump(),
- 1 = length(List),
+ 2 = length(List),
- 9 = length(folsom_metrics:get_metrics()),
+ 10 = length(folsom_metrics:get_metrics()),
?debugFmt("~n~nmetrics: ~p~n", [folsom_metrics:get_metrics()]).
@@ -92,13 +94,28 @@
{error, _, nonexistent_metric} = folsom_metrics:notify({historya, "5"}),
ok = folsom_metrics:notify(historya, <<"binary">>, history),
+
+ ?debugFmt("testing meter ...", []),
+
% simulate an interval tick
folsom_metrics_meter:tick(meter),
- [ok,ok,ok,ok,ok] = [ folsom_metrics:notify({meter, Item}) || Item <- [100, 100, 100, 100, 100]],
+ [ok,ok,ok,ok,ok] =
+ [ folsom_metrics:notify({meter, Item}) || Item <- [100, 100, 100, 100, 100]],
% simulate an interval tick
- folsom_metrics_meter:tick(meter).
+ folsom_metrics_meter:tick(meter),
+
+ ?debugFmt("testing meter reader ...", []),
+
+ % simulate an interval tick
+ folsom_metrics_meter_reader:tick(meter_reader),
+
+ [ok,ok,ok,ok,ok] =
+ [ folsom_metrics:notify({meter_reader, Item}) || Item <- [100, 100, 100, 100, 100]],
+
+ % simulate an interval tick
+ folsom_metrics_meter_reader:tick(meter_reader).
check_metrics() ->
0 = folsom_metrics:get_metric_value(counter),
@@ -125,14 +142,25 @@
Meter = folsom_metrics:get_metric_value(meter),
?debugFmt("~p", [Meter]),
ok = case proplists:get_value(one, Meter) of
- Value when Value > 1 ->
- ok;
- _ ->
- error
- end.
+ Value when Value > 1 ->
+ ok;
+ _ ->
+ error
+ end,
+
+ ?debugFmt("checking meter reader~n", []),
+ MeterReader = folsom_metrics:get_metric_value(meter_reader),
+ ?debugFmt("~p", [MeterReader]),
+ ok = case proplists:get_value(one, MeterReader) of
+ Value1 when Value1 < 1 ->
+ ok;
+ _ ->
+ error
+ end.
+
delete_metrics() ->
- 11 = length(ets:tab2list(?FOLSOM_TABLE)),
+ 12 = length(ets:tab2list(?FOLSOM_TABLE)),
ok = folsom_metrics:delete_metric(counter),
ok = folsom_metrics:delete_metric(<<"gauge">>),
@@ -152,6 +180,10 @@
ok = folsom_metrics:delete_metric(meter),
0 = length(ets:tab2list(?METER_TABLE)),
+ 1 = length(ets:tab2list(?METER_READER_TABLE)),
+ ok = folsom_metrics:delete_metric(meter_reader),
+ 0 = length(ets:tab2list(?METER_READER_TABLE)),
+
0 = length(ets:tab2list(?FOLSOM_TABLE)).
vm_metrics() ->
@@ -180,8 +212,9 @@
0 = Result.
-ensure_meter_tick_exists(Name) ->
- {state, [{Name ,{interval, _}} | _]} = folsom_meter_timer_server:dump(),
+ensure_meter_tick_exists() ->
+ {state, State} = folsom_meter_timer_server:dump(),
+ 2 = length(State),
ok.
%% internal function