1%%%
2%%% Copyright 2012 - Basho Technologies, Inc. All Rights Reserved.
3%%%
4%%% Licensed under the Apache License, Version 2.0 (the "License");
5%%% you may not use this file except in compliance with the License.
6%%% You may obtain a copy of the License at
7%%%
8%%%     http://www.apache.org/licenses/LICENSE-2.0
9%%%
10%%% Unless required by applicable law or agreed to in writing, software
11%%% distributed under the License is distributed on an "AS IS" BASIS,
12%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13%%% See the License for the specific language governing permissions and
14%%% limitations under the License.
15%%%
16
17%%%-------------------------------------------------------------------
18%%% File:      folsom_sample_slide.erl
19%%% @author    Russell Brown <russelldb@basho.com>
20%%% @doc       eunit test for folsom_sample_slide.erl
21%%% @end
22%%%------------------------------------------------------------------
23
24-module(folsom_sample_slide_test).
25
26-include_lib("eunit/include/eunit.hrl").
27-include("folsom.hrl").
28
29-define(HISTO, test_slide).
30-define(HISTO2, test_slide2).
31-define(WINDOW, 30).
32-define(DOUBLE_WINDOW, 60).
33-define(RUNTIME, 90).
34-define(READINGS, 10).
35
36slide_test_() ->
37    {setup,
38     fun () -> {ok, Apps} = application:ensure_all_started(folsom),
39               meck:new(folsom_utils),
40               Apps
41     end,
42     fun (Apps) -> meck:unload(folsom_utils),
43                   [application:stop(App) || App <- Apps]
44     end,
45     [{"Create sliding window",
46       fun create/0},
47      {"test sliding window",
48       {timeout, 30, fun exercise/0}},
49      {"resize sliding window (expand)",
50       {timeout, 30, fun expand_window/0}},
51      {"resize sliding window (shrink)",
52       {timeout, 30, fun shrink_window/0}}
53
54     ]}.
55
56create() ->
57    ok = folsom_metrics:new_histogram(?HISTO, slide, ?WINDOW),
58    #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO),
59    ?assert(is_pid(Slide#slide.server)),
60    ?assertEqual(?WINDOW, Slide#slide.window),
61    ?assertEqual(0, ets:info(Slide#slide.reservoir, size)).
62
63exercise() ->
64    %% don't want a trim to happen
65    %% unless we call trim
66    %% so kill the trim server process
67    #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO),
68    ok = folsom_sample_slide_server:stop(Slide#slide.server),
69    Moments = lists:seq(1, ?RUNTIME),
70    %% pump in 90 seconds worth of readings
71    Moment = lists:foldl(fun(_X, Tick) ->
72                                 Tock = tick(Tick),
73                                 [folsom_sample_slide:update(Slide, N) ||
74                                     N <- lists:duplicate(?READINGS, Tock)],
75                                 Tock end,
76                         0,
77                         Moments),
78    %% are all readings in the table?
79    check_table(Slide, Moments),
80    %% get values only returns last ?WINDOW seconds
81    ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
82                                                  N <- lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)])),
83    Values = lists:sort(folsom_sample_slide:get_values(Slide)),
84    ?assertEqual(ExpectedValues, Values),
85    %% trim the table
86    Trimmed = folsom_sample_slide:trim(Slide#slide.reservoir, ?WINDOW),
87    ?assertEqual((?RUNTIME - ?WINDOW - 1) * ?READINGS, Trimmed),
88    check_table(Slide, lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)),
89    %% increment the clock past the window
90    tick(Moment, ?WINDOW * 2),
91    %% get values should be empty
92    ?assertEqual([], folsom_sample_slide:get_values(Slide)),
93    %% trim, and table should be empty
94    Trimmed2 = folsom_sample_slide:trim(Slide#slide.reservoir, ?WINDOW),
95    ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?WINDOW - 1) * ?READINGS), Trimmed2),
96    check_table(Slide, []),
97    ok.
98
99expand_window() ->
100    %% create a new histogram
101    %% will leave the trim server running, as resize() needs it
102    ok = folsom_metrics:new_histogram(?HISTO2, slide, ?WINDOW),
103    #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO2),
104    Moments = lists:seq(1, ?RUNTIME ),
105    %% pump in 90 seconds worth of readings
106    Moment = lists:foldl(fun(_X, Tick) ->
107                                 Tock = tick(Tick),
108                                 [folsom_sample_slide:update(Slide, N) ||
109                                     N <- lists:duplicate(?READINGS, Tock)],
110                                 Tock end,
111                         0,
112                         Moments),
113    %% are all readings in the table?
114    check_table(Slide, Moments),
115
116    %% get values only returns last ?WINDOW seconds
117    ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
118                                                  N <- lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)])),
119    Values = lists:sort(folsom_sample_slide:get_values(Slide)),
120    ?assertEqual(ExpectedValues, Values),
121
122    %%expand the sliding window
123    NewSlide = folsom_sample_slide:resize(Slide, ?DOUBLE_WINDOW),
124
125    %% get values only returns last ?WINDOW*2 seconds
126    NewExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
127                                                  N <- lists:seq(?RUNTIME - ?DOUBLE_WINDOW, ?RUNTIME)])),
128    NewValues = lists:sort(folsom_sample_slide:get_values(NewSlide)),
129    ?assertEqual(NewExpectedValues, NewValues),
130
131
132    %% trim the table
133    Trimmed = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?DOUBLE_WINDOW),
134    ?assertEqual((?RUNTIME - ?DOUBLE_WINDOW - 1) * ?READINGS, Trimmed),
135    check_table(NewSlide, lists:seq(?RUNTIME - ?DOUBLE_WINDOW, ?RUNTIME)),
136    %% increment the clock past the window
137    tick(Moment, ?DOUBLE_WINDOW*2),
138    %% get values should be empty
139    ?assertEqual([], folsom_sample_slide:get_values(NewSlide)),
140    %% trim, and table should be empty
141    Trimmed2 = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?DOUBLE_WINDOW),
142    ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?DOUBLE_WINDOW - 1) * ?READINGS), Trimmed2),
143    check_table(NewSlide, []),
144    ok = folsom_metrics:delete_metric(?HISTO2).
145
146
147shrink_window() ->
148    %% create a new histogram
149    %% will leave the trim server running, as resize() needs it
150    ok = folsom_metrics:new_histogram(?HISTO2, slide, ?DOUBLE_WINDOW),
151    #histogram{sample=Slide} = folsom_metrics_histogram:get_value(?HISTO2),
152    Moments = lists:seq(1, ?RUNTIME ),
153    %% pump in 90 seconds worth of readings
154    Moment = lists:foldl(fun(_X, Tick) ->
155                                 Tock = tick(Tick),
156                                 [folsom_sample_slide:update(Slide, N) ||
157                                     N <- lists:duplicate(?READINGS, Tock)],
158                                 Tock end,
159                         0,
160                         Moments),
161    %% are all readings in the table?
162    check_table(Slide, Moments),
163
164    %% get values only returns last ?DOUBLE_WINDOW seconds
165    ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
166                                                  N <- lists:seq(?RUNTIME - ?DOUBLE_WINDOW, ?RUNTIME)])),
167    Values = lists:sort(folsom_sample_slide:get_values(Slide)),
168    ?assertEqual(ExpectedValues, Values),
169
170    %%shrink the sliding window
171    NewSlide = folsom_sample_slide:resize(Slide, ?WINDOW),
172
173    %% get values only returns last ?WINDOW*2 seconds
174    NewExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
175                                                  N <- lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)])),
176    NewValues = lists:sort(folsom_sample_slide:get_values(NewSlide)),
177    ?assertEqual(NewExpectedValues, NewValues),
178
179
180    %% trim the table
181    Trimmed = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?WINDOW),
182    ?assertEqual((?RUNTIME - ?WINDOW - 1) * ?READINGS, Trimmed),
183    check_table(NewSlide, lists:seq(?RUNTIME - ?WINDOW, ?RUNTIME)),
184    %% increment the clock past the window
185    tick(Moment, ?WINDOW*2),
186    %% get values should be empty
187    ?assertEqual([], folsom_sample_slide:get_values(NewSlide)),
188    %% trim, and table should be empty
189    Trimmed2 = folsom_sample_slide:trim(NewSlide#slide.reservoir, ?WINDOW),
190    ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?WINDOW - 1) * ?READINGS), Trimmed2),
191    check_table(NewSlide, []),
192    ok.
193
194tick(Moment0, IncrBy) ->
195    Moment = Moment0 + IncrBy,
196    meck:expect(folsom_utils, now_epoch, fun() ->
197                                                 Moment end),
198    Moment.
199
200tick(Moment) ->
201    tick(Moment, 1).
202
203check_table(Slide, Moments) ->
204    Tab = lists:sort(ets:tab2list(Slide#slide.reservoir)),
205    {Ks, Vs} = lists:unzip(Tab),
206    ExpectedVs = lists:sort(lists:flatten([lists:duplicate(10, N) || N <- Moments])),
207    StrippedKeys = lists:usort([X || {X, _} <- Ks]),
208    ?assertEqual(Moments, StrippedKeys),
209    ?assertEqual(ExpectedVs, lists:sort(Vs)).
210