Merge pull request #77 from vascokk/master
Ability to resize a sliding window
diff --git a/rebar b/rebar
index 77abae6..860c19e 100755
--- a/rebar
+++ b/rebar
Binary files differ
diff --git a/src/folsom_sample_slide.erl b/src/folsom_sample_slide.erl
index 631d821..e1dd455 100644
--- a/src/folsom_sample_slide.erl
+++ b/src/folsom_sample_slide.erl
@@ -28,7 +28,8 @@
update/2,
get_values/1,
moment/0,
- trim/2
+ trim/2,
+ resize/2
]).
-include("folsom.hrl").
@@ -47,6 +48,10 @@
ets:insert(Reservoir, {{Moment, Rnd}, Value}),
Sample.
+resize(Sample, NewSize) ->
+ folsom_sample_slide_server:resize(Sample#slide.server, NewSize),
+ Sample#slide{window = NewSize}.
+
get_values(#slide{window = Window, reservoir = Reservoir}) ->
Oldest = moment() - Window,
ets:select(Reservoir, [{{{'$1','_'},'$2'},[{'>=', '$1', Oldest}],['$2']}]).
diff --git a/src/folsom_sample_slide_server.erl b/src/folsom_sample_slide_server.erl
index 5986918..f02b4a2 100644
--- a/src/folsom_sample_slide_server.erl
+++ b/src/folsom_sample_slide_server.erl
@@ -31,7 +31,7 @@
-behaviour(gen_server).
%% API
--export([start_link/3, stop/1]).
+-export([start_link/3, stop/1, resize/2]).
-record(state, {sample_mod, reservoir, window}).
@@ -48,6 +48,13 @@
init([SampleMod, Reservoir, Window]) ->
{ok, #state{sample_mod = SampleMod, reservoir = Reservoir, window = Window}, timeout(Window)}.
+resize(Pid, NewWindow) ->
+ gen_server:call(Pid, {resize, NewWindow}).
+
+handle_call({resize, NewWindow}, _From, State) ->
+ NewState = State#state{window=NewWindow},
+ Reply = ok,
+ {reply, Reply, NewState, timeout(NewWindow)};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
diff --git a/src/folsom_sample_slide_uniform.erl b/src/folsom_sample_slide_uniform.erl
index 1d0204c..906e5d8 100644
--- a/src/folsom_sample_slide_uniform.erl
+++ b/src/folsom_sample_slide_uniform.erl
@@ -28,7 +28,8 @@
update/2,
get_values/1,
moment/0,
- trim/2
+ trim/2,
+ resize/2
]).
-include("folsom.hrl").
@@ -53,6 +54,10 @@
end,
Sample.
+resize(Sample, NewSize) ->
+ folsom_sample_slide_server:resize(Sample#slide.server, NewSize),
+ Sample#slide{window = NewSize}.
+
maybe_update(Reservoir, {{_Moment, Rnd}, _Value}=Obj, Size) when Rnd =< Size ->
ets:insert(Reservoir, Obj);
maybe_update(_Reservoir, _Obj, _Size) ->
diff --git a/test/folsom_sample_slide_test.erl b/test/folsom_sample_slide_test.erl
index fa31a0e..dc88bc9 100644
--- a/test/folsom_sample_slide_test.erl
+++ b/test/folsom_sample_slide_test.erl
@@ -27,7 +27,9 @@
-include("folsom.hrl").
-define(HISTO, test_slide).
+-define(HISTO2, test_slide2).
-define(WINDOW, 30).
+-define(DOUBLE_WINDOW, 60).
-define(RUNTIME, 90).
-define(READINGS, 10).
@@ -41,7 +43,12 @@
[{"Create sliding window",
fun create/0},
{"test sliding window",
- {timeout, 30, fun exercise/0}}
+ {timeout, 30, fun exercise/0}},
+ {"resize sliding window (expand)",
+ {timeout, 30, fun expand_window/0}},
+ {"resize sliding window (shrink)",
+ {timeout, 30, fun shrink_window/0}}
+
]}.
create() ->
@@ -87,6 +94,101 @@
check_table(Slide, []),
ok.
+expand_window() ->
+ %% create a new histogram
+ %% will leave the trim server running, as resize() needs it
+ ok = folsom_metrics:new_histogram(?HISTO2, slide, ?WINDOW),
+ #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO2),
+ Moments = lists:seq(1, ?RUNTIME ),
+ %% pump in 90 seconds worth of readings
+ Moment = lists:foldl(fun(_X, Tick) ->
+ Tock = tick(Tick),
+ [folsom_sample_slide:update(Slide, N) ||
+ N <- lists:duplicate(?READINGS, Tock)],
+ Tock end,
+ 0,
+ Moments),
+ %% are all readings in the table?
+ check_table(Slide, Moments),
+
+ %% get values only returns last ?WINDOW seconds
+ ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
+ N <- lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)])),
+ Values = lists:sort(folsom_sample_slide:get_values(Slide)),
+ ?assertEqual(ExpectedValues, Values),
+
+ %%expand the sliding window
+ NewSlide = folsom_sample_slide:resize(Slide, ?DOUBLE_WINDOW),
+
+ %% get values only returns last ?WINDOW*2 seconds
+ NewExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
+ N <- lists:seq(?RUNTIME - ?DOUBLE_WINDOW, ?RUNTIME)])),
+ NewValues = lists:sort(folsom_sample_slide:get_values(NewSlide)),
+ ?assertEqual(NewExpectedValues, NewValues),
+
+
+ %% trim the table
+ Trimmed = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?DOUBLE_WINDOW),
+ ?assertEqual((?RUNTIME - ?DOUBLE_WINDOW - 1) * ?READINGS, Trimmed),
+ check_table(NewSlide, lists:seq(?RUNTIME - ?DOUBLE_WINDOW, ?RUNTIME)),
+ %% increment the clock past the window
+ tick(Moment, ?DOUBLE_WINDOW*2),
+ %% get values should be empty
+ ?assertEqual([], folsom_sample_slide:get_values(NewSlide)),
+ %% trim, and table should be empty
+ Trimmed2 = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?DOUBLE_WINDOW),
+ ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?DOUBLE_WINDOW - 1) * ?READINGS), Trimmed2),
+ check_table(NewSlide, []),
+ ok = folsom_metrics:delete_metric(?HISTO2).
+
+
+shrink_window() ->
+ %% create a new histogram
+ %% will leave the trim server running, as resize() needs it
+ ok = folsom_metrics:new_histogram(?HISTO2, slide, ?DOUBLE_WINDOW),
+ #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO2),
+ Moments = lists:seq(1, ?RUNTIME ),
+ %% pump in 90 seconds worth of readings
+ Moment = lists:foldl(fun(_X, Tick) ->
+ Tock = tick(Tick),
+ [folsom_sample_slide:update(Slide, N) ||
+ N <- lists:duplicate(?READINGS, Tock)],
+ Tock end,
+ 0,
+ Moments),
+ %% are all readings in the table?
+ check_table(Slide, Moments),
+
+ %% get values only returns last ?DOUBLE_WINDOW seconds
+ ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
+ N <- lists:seq(?RUNTIME - ?DOUBLE_WINDOW, ?RUNTIME)])),
+ Values = lists:sort(folsom_sample_slide:get_values(Slide)),
+ ?assertEqual(ExpectedValues, Values),
+
+ %%shrink the sliding window
+ NewSlide = folsom_sample_slide:resize(Slide, ?WINDOW),
+
+ %% get values only returns last ?WINDOW*2 seconds
+ NewExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
+ N <- lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)])),
+ NewValues = lists:sort(folsom_sample_slide:get_values(NewSlide)),
+ ?assertEqual(NewExpectedValues, NewValues),
+
+
+ %% trim the table
+ Trimmed = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?WINDOW),
+ ?assertEqual((?RUNTIME - ?WINDOW - 1) * ?READINGS, Trimmed),
+ check_table(NewSlide, lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)),
+ %% increment the clock past the window
+ tick(Moment, ?WINDOW*2),
+ %% get values should be empty
+ ?assertEqual([], folsom_sample_slide:get_values(NewSlide)),
+ %% trim, and table should be empty
+ Trimmed2 = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?WINDOW),
+ ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?WINDOW - 1) * ?READINGS), Trimmed2),
+ check_table(NewSlide, []),
+ ok.
+
tick(Moment0, IncrBy) ->
Moment = Moment0 + IncrBy,
meck:expect(folsom_utils, now_epoch, fun() ->