1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2005-2020. 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
21-module(erts_debug_SUITE).
22-include_lib("common_test/include/ct.hrl").
23-include_lib("common_test/include/ct_event.hrl").
24
25-export([all/0, suite/0, groups/0,
26         test_size/1,flat_size_big/1,df/1,term_type/1,
27         instructions/1, stack_check/1, alloc_blocks_size/1,
28         interpreter_size_bench/1]).
29
30-export([do_alloc_blocks_size/0]).
31
32suite() ->
33    [{ct_hooks,[ts_install_cth]},
34     {timetrap, {minutes, 2}}].
35
36all() ->
37    [test_size, flat_size_big, df, instructions, term_type,
38     stack_check, alloc_blocks_size].
39
40groups() ->
41    [{interpreter_size_bench, [], [interpreter_size_bench]}].
42
43interpreter_size_bench(_Config) ->
44    Size = erts_debug:interpreter_size(),
45    ct_event:notify(#event{name=benchmark_data,
46                           data=[{value,Size}]}),
47    {comment,integer_to_list(Size)++" bytes"}.
48
49%% White box testing of term heap sizes
50test_size(Config) when is_list(Config) ->
51    ConsCell1 = id([a|b]),
52    ConsCell2 = id(ConsCell1),
53    ConsCellSz = 2,
54
55    0 = do_test_size([]),
56    0 = do_test_size(42),
57    ConsCellSz = do_test_size(ConsCell1),
58    1 = do_test_size({}),
59    2 = do_test_size({[]}),
60    3 = do_test_size({a,b}),
61    7 = do_test_size({a,[b,c]}),
62    8 = do_test_size(#{b => 2,c => 3}),
63    4 = do_test_size(#{}),
64    32 = do_test_size(#{b => 2,c => 3,txt => "hello world"}),
65
66    true = do_test_size(maps:from_list([{I,I}||I<-lists:seq(1,256)])) >= map_size_lower_bound(256),
67    true = do_test_size(maps:from_list([{I,I}||I<-lists:seq(1,4096)])) >= map_size_lower_bound(4096),
68    true = do_test_size(maps:from_list([{I,I}||I<-lists:seq(1,254)])) >= map_size_lower_bound(254),
69    true = do_test_size(maps:from_list([{I,I}||I<-lists:seq(1,239)])) >= map_size_lower_bound(239),
70
71    Const = id(42),
72    AnotherConst = id(7),
73
74    %% Fun environment size = 0 (the smallest fun possible)
75    SimplestFun = fun() -> ok end,
76    FunSz0 = 6,
77    FunSz0 = do_test_size(SimplestFun),
78
79    %% Fun environment size = 1
80    FunSz1 = do_test_size(fun() -> Const end),
81    FunSz1 = FunSz0 + 1,
82
83    %% Fun environment size = 2
84    FunSz2 = do_test_size(fun() -> Const+AnotherConst end),
85    FunSz2 = FunSz1 + 1,
86
87    FunSz1 = do_test_size(fun() -> ConsCell1 end) - do_test_size(ConsCell1),
88
89    2 = do_test_size(fun lists:sort/1),
90
91    Arch = 8 * erlang:system_info({wordsize, external}),
92    case {Arch, do_test_size(mk_ext_pid({a@b, 1}, 17, 42))} of
93	{32, 5} -> ok;
94	{64, 4} -> ok
95    end,
96    case {Arch, do_test_size(mk_ext_port({a@b, 1}, 1742))} of
97	{32, 5} -> ok;
98	{64, 4} -> ok
99    end,
100    case {Arch, do_test_size(make_ref())} of
101	{32, 4} -> ok;
102	{64, 3} -> ok
103    end,
104    case {Arch, do_test_size(mk_ext_ref({a@b, 1}, [42,43,44]))} of
105	{32, 6} -> ok;
106	{64, 5} -> ok
107    end,
108    3 = do_test_size(atomics:new(1,[])), % Magic ref
109
110    3 = do_test_size(<<1,2,3>>),       % ErlHeapBin
111    case {Arch, do_test_size(<<0:(8*64)>>)} of   % ERL_ONHEAP_BIN_LIMIT
112	{32, 18} -> ok;
113	{64, 10} -> ok
114    end,
115    6 = do_test_size(<<0:(8*65)>>),    % ProcBin
116    8 = do_test_size(<<5:7>>),         % ErlSubBin + ErlHeapBin
117    11 = do_test_size(<<0:(8*80+1)>>), % ErlSubBin + ProcBin
118
119    %% Test shared data structures.
120    do_test_size([ConsCell1|ConsCell1],
121        	 3*ConsCellSz,
122        	 2*ConsCellSz),
123    do_test_size(fun() -> {ConsCell1,ConsCell2} end,
124        	 FunSz2 + 2*ConsCellSz,
125        	 FunSz2 + ConsCellSz),
126    do_test_size({SimplestFun,SimplestFun},
127        	 2*FunSz0+do_test_size({a,b}),
128        	 FunSz0+do_test_size({a,b})),
129
130    M = id(#{ "atom" => first, i => 0}),
131    do_test_size([M,M#{ "atom" := other },M#{i := 42}],54,32),
132    ok.
133
134do_test_size(Term) ->
135    Sz = erts_debug:flat_size(Term),
136    Sz = erts_debug:size(Term).
137
138do_test_size(Term, FlatSz, Sz) ->
139    FlatSz = erts_debug:flat_size(Term),
140    Sz = erts_debug:size(Term).
141
142map_size_lower_bound(N) ->
143    %% this est. is a bit lower that actual lower bound
144    %% number of internal nodes
145    T = (N - 1) div 15,
146    %% total words
147    2 + 17 * T + 2 * N.
148
149flat_size_big(Config) when is_list(Config) ->
150    %% Build a term whose external size only fits in a big num (on 32-bit CPU).
151    flat_size_big_1(16#11111111111111117777777777777777888889999, 0, 16#FFFFFFF).
152
153flat_size_big_1(Term, Size0, Limit) when Size0 < Limit ->
154    case erts_debug:flat_size(Term) of
155	Size when is_integer(Size), Size0 < Size ->
156	    io:format("~p", [Size]),
157	    flat_size_big_1([Term|Term], Size, Limit)
158    end;
159flat_size_big_1(_, _, _) -> ok.
160
161
162term_type(Config) when is_list(Config) ->
163    Ts = [{fixnum, 1},
164          {fixnum, -1},
165          {bignum, 1 bsl 300},
166          {bignum, -(1 bsl 300)},
167          {hfloat, 0.0},
168          {hfloat, 0.0/-1},
169          {hfloat, 1.0/(1 bsl 302)},
170          {hfloat, 1.0*(1 bsl 302)},
171          {hfloat, -1.0/(1 bsl 302)},
172          {hfloat, -1.0*(1 bsl 302)},
173          {hfloat, 3.1416},
174          {hfloat, 1.0e18},
175          {hfloat, -3.1416},
176          {hfloat, -1.0e18},
177
178          {heap_binary, <<1,2,3>>},
179          {refc_binary, <<0:(8*80)>>},
180          {sub_binary,  <<5:7>>},
181
182          {flatmap, #{ a => 1}},
183          {hashmap, maps:from_list([{I,I}||I <- lists:seq(1,76)])},
184
185          {list, [1,2,3]},
186          {nil, []},
187          {tuple, {1,2,3}},
188          {tuple, {}},
189
190          {export, fun lists:sort/1},
191          {'fun', fun() -> ok end},
192          {pid, self()},
193          {atom, atom}],
194    lists:foreach(fun({E,Val}) ->
195                          R = erts_internal:term_type(Val),
196                          io:format("expecting term type ~w, got ~w (~p)~n", [E,R,Val]),
197                          E = R
198                  end, Ts),
199    ok.
200
201
202df(Config) when is_list(Config) ->
203    P0 = pps(),
204    PrivDir = proplists:get_value(priv_dir, Config),
205    ok = file:set_cwd(PrivDir),
206
207    AllLoaded = [M || {M,_} <- code:all_loaded()],
208    {Pid,Ref} = spawn_monitor(fun() -> df_smoke(AllLoaded) end),
209    receive
210	{'DOWN',Ref,process,Pid,Status} ->
211	    normal = Status
212    after 20*1000 ->
213	    %% Not finished (i.e. a slow computer). Stop now.
214	    Pid ! stop,
215	    receive
216		{'DOWN',Ref,process,Pid,Status} ->
217		    normal = Status,
218		    io:format("...")
219	    end
220    end,
221    io:nl(),
222    _ = [_ = file:delete(atom_to_list(M) ++ ".dis") ||
223	    M <- AllLoaded],
224
225    true = (P0 == pps()),
226    ok.
227
228stack_check(Config) when is_list(Config) ->
229    erts_debug:set_internal_state(available_internal_state,true),
230    %% Recurses on the C stack until stacklimit is reached. That
231    %% is, tests that the stack limit functionality works (used
232    %% by PCRE). VM will crash if it doesn't work...
233    Size = erts_debug:get_internal_state(stack_check),
234    erts_debug:set_internal_state(available_internal_state,false),
235    true = (is_integer(Size) and (Size > 0)),
236    {comment, "Stack size: "++integer_to_list(Size)++" bytes"}.
237
238df_smoke([M|Ms]) ->
239    io:format("~p", [M]),
240    erts_debug:df(M),
241    receive
242	stop ->
243	    ok
244    after 0 ->
245	    df_smoke(Ms)
246    end;
247df_smoke([]) -> ok.
248
249pps() ->
250    {erlang:ports()}.
251
252instructions(Config) when is_list(Config) ->
253    Is = erts_debug:instructions(),
254    _ = [list_to_atom(I) || I <- Is],
255    ok.
256
257alloc_blocks_size(Config) when is_list(Config) ->
258    F = fun(Args) ->
259                Node = start_slave(Args),
260                ok = rpc:call(Node, ?MODULE, do_alloc_blocks_size, []),
261                true = test_server:stop_node(Node)
262        end,
263    case test_server:is_asan() of
264	false -> F("+Meamax");
265	true -> skip
266    end,
267    F("+Meamin"),
268    F(""),
269    ok.
270
271do_alloc_blocks_size() ->
272    _ = erts_debug:alloc_blocks_size(binary_alloc),
273    ok.
274
275start_slave(Args) ->
276    Name = ?MODULE_STRING ++ "_slave",
277    Pa = filename:dirname(code:which(?MODULE)),
278    {ok, Node} = test_server:start_node(list_to_atom(Name),
279                                        slave,
280                                        [{args, "-pa " ++ Pa ++ " " ++ Args}]),
281    Node.
282
283id(I) ->
284    I.
285
286mk_ext_pid({NodeName, Creation}, Number, Serial) ->
287    erts_test_utils:mk_ext_pid({NodeName, Creation}, Number, Serial).
288
289mk_ext_port({NodeName, Creation}, Number) ->
290    erts_test_utils:mk_ext_port({NodeName, Creation}, Number).
291
292mk_ext_ref({NodeName, Creation}, Numbers) ->
293    erts_test_utils:mk_ext_ref({NodeName, Creation}, Numbers).
294