1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(counters_SUITE).
21
22-include_lib("common_test/include/ct.hrl").
23
24-export([suite/0, all/0]).
25-export([basic/1, bad/1, limits/1, indep/1, write_concurrency/1]).
26
27suite() -> [{ct_hooks,[ts_install_cth]}].
28
29all() ->
30    [basic, bad, limits, indep, write_concurrency].
31
32basic(Config) when is_list(Config) ->
33    Size = 10,
34    [begin
35         Ref = counters:new(Size,[Type]),
36         #{size:=Size, memory:=Memory} = counters:info(Ref),
37         check_memory(Type, Memory, Size),
38         [basic_do(Ref, Ix) || Ix <- lists:seq(1, Size)]
39     end
40     || Type <- [atomics, write_concurrency]],
41    ok.
42
43basic_do(Ref, Ix) ->
44    0 = counters:get(Ref, Ix),
45    ok = counters:add(Ref, Ix, 3),
46    3  = counters:get(Ref, Ix),
47    ok = counters:add(Ref, Ix, 14),
48    17  = counters:get(Ref, Ix),
49    ok = counters:add(Ref, Ix, -20),
50    -3  = counters:get(Ref, Ix),
51    ok = counters:add(Ref, Ix, 100),
52    97 = counters:get(Ref, Ix),
53    ok = counters:sub(Ref, Ix, 20),
54    77 = counters:get(Ref, Ix),
55    ok = counters:sub(Ref, Ix, -10),
56    87 = counters:get(Ref, Ix),
57    ok = counters:put(Ref, Ix, 0),
58    0 = counters:get(Ref, Ix),
59    ok = counters:put(Ref, Ix, 123),
60    123 = counters:get(Ref, Ix),
61    ok = counters:put(Ref, Ix, -321),
62    -321 = counters:get(Ref, Ix),
63    ok.
64
65check_memory(atomics, Memory, Size) ->
66    {_,true} = {Memory, Memory > Size*8},
67    {_,true} = {Memory, Memory < Size*max_atomic_sz() + 100};
68check_memory(write_concurrency, Memory, Size) ->
69    NWords = erlang:system_info(schedulers) + 1,
70    {_,true} = {Memory, Memory > NWords*Size*8},
71    {_,true} = {Memory, Memory < NWords*(Size+7)*max_atomic_sz() + 100}.
72
73max_atomic_sz() ->
74    case erlang:system_info({wordsize, external}) of
75        4 -> 16;
76        8 ->
77            EI = erlang:system_info(ethread_info),
78            case lists:keyfind("64-bit native atomics", 1, EI) of
79                {_, "no", _} -> 16;
80                _ -> 8
81            end
82    end.
83
84bad(Config) when is_list(Config) ->
85    {'EXIT',{badarg,_}} = (catch counters:new(0,[])),
86    {'EXIT',{badarg,_}} = (catch counters:new(10,[bad])),
87    {'EXIT',{badarg,_}} = (catch counters:new(10,[atomic, bad])),
88    {'EXIT',{badarg,_}} = (catch counters:new(10,[write_concurrency | bad])),
89    Ref = counters:new(10,[]),
90    {'EXIT',{badarg,_}} = (catch counters:get(1742, 7)),
91    {'EXIT',{badarg,_}} = (catch counters:get(make_ref(), 7)),
92    {'EXIT',{badarg,_}} = (catch counters:get(Ref, -1)),
93    {'EXIT',{badarg,_}} = (catch counters:get(Ref, 0)),
94    {'EXIT',{badarg,_}} = (catch counters:get(Ref, 11)),
95    {'EXIT',{badarg,_}} = (catch counters:get(Ref, 7.0)),
96    ok.
97
98
99limits(Config) when is_list(Config) ->
100    limits_do(counters:new(1,[atomics])),
101    limits_do(counters:new(1,[write_concurrency])),
102    ok.
103
104limits_do(Ref) ->
105    Bits = 64,
106    Max = (1 bsl (Bits-1)) - 1,
107    Min = -(1 bsl (Bits-1)),
108
109    0 = counters:get(Ref, 1),
110    ok = counters:put(Ref, 1, Max),
111    Max = counters:get(Ref, 1),
112    ok = counters:add(Ref, 1, 1),
113    Min = counters:get(Ref, 1),
114    ok  = counters:sub(Ref, 1, 1),
115    Max = counters:get(Ref, 1),
116    ok = counters:put(Ref, 1, Min),
117    Min = counters:get(Ref, 1),
118
119    IncrMax = (Max bsl 1) bor 1,
120    ok = counters:put(Ref, 1, 0),
121    ok = counters:add(Ref, 1, IncrMax),
122    -1 = counters:get(Ref, 1),
123    {'EXIT',{badarg,_}} = (catch counters:add(Ref, 1, IncrMax+1)),
124    {'EXIT',{badarg,_}} = (catch counters:add(Ref, 1, Min-1)),
125    {'EXIT',{badarg,_}} = (catch counters:put(Ref, 1, Max+1)),
126    {'EXIT',{badarg,_}} = (catch counters:add(Ref, 1, Min-1)),
127    ok.
128
129
130%% Verify that independent workers, using different counters
131%% within the same array, do not interfere with each other.
132indep(Config) when is_list(Config) ->
133    NScheds = erlang:system_info(schedulers),
134    Ref = counters:new(NScheds,[write_concurrency]),
135    Rounds = 100,
136    Papa = self(),
137    Pids = [spawn_opt(fun () ->
138                               Val = I*197,
139                               counters:put(Ref, I, Val),
140                               indep_looper(Rounds, Ref, I, Val),
141                               Papa ! {self(), done}
142                       end,
143                      [link, {scheduler, I}])
144            || I <- lists:seq(1, NScheds)],
145    [receive {P,done} -> ok end || P <- Pids],
146    ok.
147
148indep_looper(0, _, _ , _) ->
149    ok;
150indep_looper(N, Ref, I, Val0) ->
151    %%io:format("Val0 = ~p\n", [Val0]),
152    Val0 = counters:get(Ref, I),
153    Val1 = indep_adder(Ref, I, Val0),
154    indep_subber(Ref, I, Val1),
155    Val2 = N*7 + I,
156    counters:put(Ref, I, Val2),
157    indep_looper(N-1, Ref, I, Val2).
158
159indep_adder(Ref, I, Val) when Val < (1 bsl 62) ->
160    %%io:format("adder Val = ~p\n", [Val]),
161    Incr = abs(Val div 2) + I + 984735,
162    counters:add(Ref, I, Incr),
163    Res = Val + Incr,
164    Res = counters:get(Ref, I),
165    indep_adder(Ref, I, Res);
166indep_adder(_Ref, _I, Val) ->
167    Val.
168
169indep_subber(Ref, I, Val) when Val > -(1 bsl 62) ->
170    %%io:format("subber Val = ~p\n", [Val]),
171    Decr = (abs(Val div 2) + I + 725634),
172    counters:sub(Ref, I, Decr),
173    Res = Val - Decr,
174    Res = counters:get(Ref, I),
175    indep_subber(Ref, I, Res);
176indep_subber(_Ref, _I, Val) ->
177    Val.
178
179
180
181%% Verify write_concurrency yields correct results.
182write_concurrency(Config) when is_list(Config) ->
183    rand:seed(exs1024s),
184    io:format("*** SEED: ~p ***\n", [rand:export_seed()]),
185    NScheds = erlang:system_info(schedulers),
186    Size = 100,
187    Ref = counters:new(Size,[write_concurrency]),
188    Rounds = 1000,
189    Papa = self(),
190    Pids = [spawn_opt(fun Worker() ->
191                              receive
192                                  {go, Ix, Incr} ->
193                                      wc_looper(Rounds, Ref, Ix, Incr),
194                                      Papa ! {self(), done, Rounds*Incr},
195                                      Worker();
196                                  stop ->
197                                      ok
198                              end
199                       end,
200                      [link, {scheduler, N}])
201            || N <- lists:seq(1, NScheds)],
202    [begin
203         Base = rand_log64(),
204         counters:put(Ref, Index, Base),
205         SendList = [{P,{go, Index, rand_log64()}} || P <- Pids],
206         [P ! Msg || {P,Msg} <- SendList],
207         Added = lists:sum([receive {P,done,Contrib} -> Contrib end || P <- Pids]),
208         Result = mask_sint64(Base+Added),
209         {_,Result} = {Result, counters:get(Ref, Index)}
210     end
211     || Index <- lists:seq(1, Size)],
212
213    [begin unlink(P), P ! stop end || P <- Pids],
214    ok.
215
216wc_looper(0, _, _, _) ->
217    ok;
218wc_looper(N, Ref, Ix, Incr) ->
219    counters:add(Ref, Ix, Incr),
220    wc_looper(N-1, Ref, Ix, Incr).
221
222mask_sint64(X) ->
223    SMask = 1 bsl 63,
224    UMask = SMask - 1,
225    (X band UMask) - (X band SMask).
226
227%% A random signed 64-bit integer
228%% with a uniformly distributed number of significant bits.
229rand_log64() ->
230    Uint = round(math:pow(2, rand:uniform()*63)),
231    case rand:uniform(2) of
232        1 -> -Uint;
233        2 -> Uint
234    end.
235